carray 3.0.3 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +264 -0
- data/README.md +3 -2
- data/carray.gemspec +1 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +1 -6
- data/ext/ca_axis_group.c +27 -27
- data/ext/ca_binop_dispatch.c +1 -29
- data/ext/ca_binop_dispatch.h +0 -1
- data/ext/ca_categorical_iterator.c +205 -134
- data/ext/ca_compare.c +7 -18
- data/ext/ca_group_iter.c +16 -17
- data/ext/ca_kernel_iterator.c +1234 -1994
- data/ext/ca_kernel_iterator.h +391 -437
- data/ext/ca_monop_dispatch.c +82 -59
- data/ext/ca_monop_dispatch.h +1 -0
- data/ext/ca_obj_array.c +96 -39
- data/ext/ca_obj_bincmp.c +24 -21
- data/ext/ca_obj_binop.c +15 -26
- data/ext/ca_obj_bitarray.c +3 -2
- data/ext/ca_obj_bitfield.c +5 -6
- data/ext/ca_obj_block.c +18 -9
- data/ext/ca_obj_byte_swap.c +5 -6
- data/ext/ca_obj_const_string.c +173 -19
- data/ext/ca_obj_face.c +238 -57
- data/ext/ca_obj_face.h +19 -0
- data/ext/ca_obj_fake.c +15 -29
- data/ext/ca_obj_farray.c +6 -3
- data/ext/ca_obj_field.c +17 -5
- data/ext/ca_obj_fixlen_string.c +2 -3
- data/ext/ca_obj_grid.c +406 -132
- data/ext/ca_obj_meld.c +172 -34
- data/ext/ca_obj_moncmp.c +23 -8
- data/ext/ca_obj_monop.c +67 -57
- data/ext/ca_obj_object.c +157 -51
- data/ext/ca_obj_record.c +12 -7
- data/ext/ca_obj_reduce.c +13 -8
- data/ext/ca_obj_refer.c +25 -27
- data/ext/ca_obj_remap.c +30 -9
- data/ext/ca_obj_repeat.c +9 -10
- data/ext/ca_obj_roll.c +6 -5
- data/ext/ca_obj_select.c +14 -9
- data/ext/ca_obj_select_axis.c +15 -47
- data/ext/ca_obj_shift.c +11 -1
- data/ext/ca_obj_stack.c +179 -34
- data/ext/ca_obj_stride.c +162 -115
- data/ext/ca_obj_string.c +71 -4
- data/ext/ca_obj_tile.c +6 -5
- data/ext/ca_obj_time.c +22 -6
- data/ext/ca_obj_timedelta.c +22 -6
- data/ext/ca_obj_transpose.c +5 -3
- data/ext/ca_obj_triop.c +23 -32
- data/ext/ca_obj_window.c +190 -26
- data/ext/ca_sweep_engine.c +2 -2
- data/ext/caframe_csv_reader.c +80 -1
- data/ext/carray.h +181 -75
- data/ext/carray_access.c +184 -96
- data/ext/carray_address_basis.c +343 -87
- data/ext/carray_attribute.c +89 -6
- data/ext/carray_bincount.c +10 -8
- data/ext/carray_broadcast.c +13 -8
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +108 -0
- data/ext/carray_cast.c +142 -24
- data/ext/carray_cast_func.rb +46 -6
- data/ext/carray_class.c +4 -3
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +16 -7
- data/ext/carray_copy.c +51 -14
- data/ext/carray_core.c +147 -55
- data/ext/carray_count.c +50 -20
- data/ext/carray_element.c +76 -33
- data/ext/carray_factorize.c +118 -64
- data/ext/carray_generate.c +191 -50
- data/ext/carray_histogram.c +62 -17
- data/ext/carray_hold.c +12 -15
- data/ext/carray_index_classifier.c +388 -176
- data/ext/carray_internal.h +336 -29
- data/ext/carray_kernels_bincmp.c +4453 -0
- data/ext/carray_kernels_binop.c +11368 -0
- data/ext/carray_kernels_init.c +1734 -0
- data/ext/carray_kernels_moncmp.c +2104 -0
- data/ext/carray_kernels_monop.c +18369 -0
- data/ext/carray_kernels_reduce_aggregate.c +23818 -0
- data/ext/carray_kernels_reduce_boolean.c +406 -0
- data/ext/carray_kernels_reduce_cumulative.c +10086 -0
- data/ext/carray_kernels_reduce_extreme.c +17634 -0
- data/ext/carray_kernels_reduce_variance.c +3849 -0
- data/ext/carray_kernels_scan.c +4302 -0
- data/ext/carray_kernels_search.c +33829 -0
- data/ext/carray_kernels_sort.c +10906 -0
- data/ext/carray_kernels_triop.c +1864 -0
- data/ext/carray_key_windows.c +216 -0
- data/ext/carray_lazy.c +170 -111
- data/ext/carray_mask.c +292 -72
- data/ext/carray_median_percentile.c +82 -28
- data/ext/carray_memory_view.c +669 -561
- data/ext/carray_operator.c +124 -47
- data/ext/carray_order.c +384 -76
- data/ext/carray_partition.c +28 -27
- data/ext/carray_random.c +67 -13
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +63 -38
- data/ext/carray_segment.c +3 -3
- data/ext/carray_slab.c +91 -28
- data/ext/carray_sort.c +35 -35
- data/ext/carray_struct.c +19 -18
- data/ext/carray_test.c +26 -5
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +246 -45
- data/ext/mk_call_cfunc.rb +19 -2
- data/ext/mkkernel.rb +198 -240
- data/ext/ruby_carray.c +10 -4
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +126 -22
- data/lib/carray/autoload_carray.rb +8 -31
- data/lib/carray/autoload_method_extension.rb +14 -5
- data/lib/carray/axis_group.rb +255 -106
- data/lib/carray/basics.rb +78 -10
- data/lib/carray/bincount_nd.rb +12 -2
- data/lib/carray/block_iterator.rb +126 -23
- data/lib/carray/boolean_reduce.rb +17 -17
- data/lib/carray/categorical.rb +165 -43
- data/lib/carray/categorical_iterator.rb +130 -27
- data/lib/carray/complex.rb +5 -5
- data/lib/carray/conditional.rb +67 -18
- data/lib/carray/const_string.rb +14 -5
- data/lib/carray/construct.rb +15 -11
- data/lib/carray/data_type_extension.rb +1 -0
- data/lib/carray/distance_limit.rb +77 -0
- data/lib/carray/fixlen_string.rb +7 -1
- data/lib/carray/frame/column_verbs.rb +70 -0
- data/lib/carray/frame/convert.rb +69 -0
- data/lib/carray/frame/csv_parser.rb +111 -17
- data/lib/carray/frame/describe.rb +3 -2
- data/lib/carray/frame/frame.rb +246 -21
- data/lib/carray/frame/group.rb +510 -95
- data/lib/carray/frame/io.rb +115 -28
- data/lib/carray/frame/join.rb +312 -51
- data/lib/carray/frame/records.rb +83 -7
- data/lib/carray/frame/reshape.rb +42 -35
- data/lib/carray/frame/sort.rb +7 -19
- data/lib/carray/frame/verbs.rb +188 -48
- data/lib/carray/frame/windows.rb +117 -0
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +11 -4
- data/lib/carray/fusion.rb +40 -16
- data/lib/carray/histogram.rb +6 -1
- data/lib/carray/inspect.rb +31 -8
- data/lib/carray/iterator.rb +85 -7
- data/lib/carray/key_windows.rb +620 -0
- data/lib/carray/lazy.rb +182 -183
- data/lib/carray/mask_gap_fill.rb +63 -57
- data/lib/carray/meld_reduce.rb +12 -14
- data/lib/carray/methods/bin.rb +26 -18
- data/lib/carray/methods/bincount.rb +16 -0
- data/lib/carray/methods/bit_string.rb +1 -1
- data/lib/carray/methods/choose.rb +16 -3
- data/lib/carray/methods/composition.rb +3 -1
- data/lib/carray/methods/gather_nd.rb +14 -5
- data/lib/carray/methods/insert_block.rb +2 -2
- data/lib/carray/methods/is_between.rb +59 -0
- data/lib/carray/methods/is_in.rb +53 -36
- data/lib/carray/methods/join.rb +1 -6
- data/lib/carray/methods/lag.rb +49 -0
- data/lib/carray/methods/locate_addr.rb +47 -51
- data/lib/carray/methods/mode.rb +1 -1
- data/lib/carray/methods/nunique.rb +11 -6
- data/lib/carray/methods/resize.rb +3 -3
- data/lib/carray/methods/value_counts.rb +4 -11
- data/lib/carray/record_order.rb +3 -3
- data/lib/carray/rng.rb +3 -0
- data/lib/carray/runtime.rb +53 -8
- data/lib/carray/segment_iterator.rb +313 -82
- data/lib/carray/serialize.rb +24 -10
- data/lib/carray/slab_iterator.rb +50 -0
- data/lib/carray/string.rb +105 -5
- data/lib/carray/string_operation_extension.rb +8 -1
- data/lib/carray/struct.rb +9 -21
- data/lib/carray/struct_builder.rb +19 -8
- data/lib/carray/time.rb +726 -104
- data/lib/carray/window_iterator.rb +122 -16
- data/lib/carray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +4 -0
- data/yard-stubs/ca_obj_grid.rb +21 -10
- data/yard-stubs/ca_obj_refer.rb +2 -1
- data/yard-stubs/ca_obj_roll.rb +2 -2
- data/yard-stubs/ca_obj_shift.rb +5 -1
- data/yard-stubs/ca_obj_tile.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +8 -3
- data/yard-stubs/carray_access.rb +6 -1
- data/yard-stubs/carray_attribute.rb +0 -5
- data/yard-stubs/carray_cast.rb +10 -0
- data/yard-stubs/carray_conversion.rb +2 -1
- data/yard-stubs/carray_copy.rb +4 -2
- data/yard-stubs/carray_count.rb +4 -4
- data/yard-stubs/carray_element.rb +10 -4
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_mask.rb +26 -2
- data/yard-stubs/carray_math.rb +27 -1
- data/yard-stubs/carray_median_percentile.rb +5 -2
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +29 -18
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +14 -9
- data/yard-stubs/carray_slab.rb +9 -2
- data/yard-stubs/carray_sort.rb +6 -5
- data/yard-stubs/carray_test.rb +5 -3
- metadata +25 -3
- data/lib/carray/arrow_tensor.rb +0 -401
- data/lib/carray/table.rb +0 -108
data/lib/carray/mask_gap_fill.rb
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# `strip_mask(:na)`). Passing both raises ArgumentError.
|
|
16
16
|
#
|
|
17
17
|
# Design / rationale: devel/PROPOSAL_UNMASK_FILL_METHOD.md. The forward
|
|
18
|
-
# hold is the C primitive #
|
|
18
|
+
# hold is the C primitive #hold_forward; backward = flip -> forward -> flip,
|
|
19
19
|
# flatten = flatten -> hold -> reshape, and :linear composes
|
|
20
20
|
# linear_section / linear_fetch. User-facing YARD docs live in
|
|
21
21
|
# yard-stubs/carray_mask.rb.
|
|
@@ -29,50 +29,50 @@
|
|
|
29
29
|
|
|
30
30
|
class CArray
|
|
31
31
|
|
|
32
|
-
# Preserve the C constant-fill primitives under private names so the
|
|
33
|
-
# Ruby wrappers can delegate the non-method path unchanged.
|
|
34
|
-
alias __unmask_const__ unmask
|
|
35
|
-
alias __strip_mask_const__ strip_mask
|
|
36
|
-
private :__unmask_const__, :__strip_mask_const__
|
|
37
|
-
|
|
38
32
|
# Sentinel for "no positional fill value given" (distinct from any real
|
|
39
33
|
# fill value, including nil / false / a Symbol).
|
|
40
34
|
MASK_FILL_UNSET = ::Object.new
|
|
35
|
+
Ractor.make_shareable(MASK_FILL_UNSET)
|
|
41
36
|
private_constant :MASK_FILL_UNSET
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
# unmask / strip_mask with method:, in front of the C constant-fill
|
|
39
|
+
# primitives, which they reach with super.
|
|
40
|
+
module GapFill
|
|
41
|
+
def unmask (fill = MASK_FILL_UNSET, method: nil, axis: nil)
|
|
42
|
+
if method
|
|
43
|
+
unless fill.equal?(MASK_FILL_UNSET)
|
|
44
|
+
raise ArgumentError,
|
|
45
|
+
"unmask: pass either a constant fill value or method:, not both"
|
|
46
|
+
end
|
|
47
|
+
held = gap_fill(method, axis, "unmask")
|
|
48
|
+
# Copy the filled values in place (a Face takes a Face of its own kind
|
|
49
|
+
# as it is).
|
|
50
|
+
value[] = held.value
|
|
51
|
+
if held.has_mask?
|
|
52
|
+
self.mask = held.mask # residual leading/trailing mask
|
|
53
|
+
else
|
|
54
|
+
super() # fully filled: drop the mask
|
|
55
|
+
end
|
|
56
|
+
return self
|
|
57
57
|
end
|
|
58
|
-
|
|
58
|
+
fill.equal?(MASK_FILL_UNSET) ? super() : super(fill)
|
|
59
59
|
end
|
|
60
|
-
fill.equal?(MASK_FILL_UNSET) ? __unmask_const__ : __unmask_const__(fill)
|
|
61
|
-
end
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
def strip_mask (fill = MASK_FILL_UNSET, method: nil, axis: nil)
|
|
62
|
+
if method
|
|
63
|
+
unless fill.equal?(MASK_FILL_UNSET)
|
|
64
|
+
raise ArgumentError,
|
|
65
|
+
"strip_mask: pass either a constant fill value or method:, not both"
|
|
66
|
+
end
|
|
67
|
+
return carry_attrs(gap_fill(method, axis, "strip_mask"))
|
|
68
68
|
end
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
if fill.equal?(MASK_FILL_UNSET)
|
|
70
|
+
raise ArgumentError, "strip_mask: a fill value is required (or method:)"
|
|
71
|
+
end
|
|
72
|
+
carry_attrs(super(fill))
|
|
73
73
|
end
|
|
74
|
-
__strip_mask_const__(fill)
|
|
75
74
|
end
|
|
75
|
+
prepend GapFill
|
|
76
76
|
|
|
77
77
|
private
|
|
78
78
|
|
|
@@ -80,15 +80,15 @@ class CArray
|
|
|
80
80
|
# unfillable cells stay masked; a fully filled result carries no mask
|
|
81
81
|
# (hold allocates the output mask only when a leading run goes UNDEF;
|
|
82
82
|
# :linear masks only the exterior via mask_invalid).
|
|
83
|
-
def
|
|
83
|
+
def gap_fill (method, axis, name)
|
|
84
84
|
axis = normalize_axis(axis, name) unless axis.nil?
|
|
85
85
|
case method
|
|
86
86
|
when :forward, :ffill
|
|
87
|
-
|
|
87
|
+
hold_along_axis(axis, false)
|
|
88
88
|
when :backward, :bfill
|
|
89
|
-
|
|
89
|
+
hold_along_axis(axis, true)
|
|
90
90
|
when :linear
|
|
91
|
-
|
|
91
|
+
gap_fill_linear(axis)
|
|
92
92
|
else
|
|
93
93
|
raise ArgumentError,
|
|
94
94
|
"unmask/strip_mask: unknown method: #{method.inspect} " \
|
|
@@ -97,20 +97,20 @@ class CArray
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
# Forward (backward = false) or backward (true) hold along `axis`, already
|
|
100
|
-
# normalised by
|
|
100
|
+
# normalised by gap_fill (axis nil = flatten). Backward reuses the forward primitive on the
|
|
101
101
|
# reversed view; flatten flattens, holds axis 0, reshapes back.
|
|
102
|
-
def
|
|
102
|
+
def hold_along_axis (axis, backward)
|
|
103
103
|
if axis.nil?
|
|
104
104
|
flat = flatten
|
|
105
105
|
flat = flat.reverse if backward
|
|
106
|
-
held = flat.send(:
|
|
106
|
+
held = flat.send(:hold_forward, 0)
|
|
107
107
|
held = held.reverse if backward
|
|
108
108
|
held.reshape(*shape)
|
|
109
109
|
else
|
|
110
110
|
if backward
|
|
111
|
-
flip(axis).send(:
|
|
111
|
+
flip(axis).send(:hold_forward, axis).flip(axis)
|
|
112
112
|
else
|
|
113
|
-
send(:
|
|
113
|
+
send(:hold_forward, axis)
|
|
114
114
|
end
|
|
115
115
|
end
|
|
116
116
|
end
|
|
@@ -121,9 +121,9 @@ class CArray
|
|
|
121
121
|
# linear_fetch, which keeps its unit and rounds to its grid. Defining one
|
|
122
122
|
# is how a Face says it interpolates: without it, the core linear_fetch
|
|
123
123
|
# reads the Face's storage and fails from inside.
|
|
124
|
-
def
|
|
124
|
+
def gap_fill_linear (axis)
|
|
125
125
|
if face?
|
|
126
|
-
unless
|
|
126
|
+
unless interpolable?
|
|
127
127
|
raise ArgumentError,
|
|
128
128
|
"unmask/strip_mask(method: :linear): #{self.class} does not " \
|
|
129
129
|
"interpolate (it defines no linear_fetch of its own); " \
|
|
@@ -135,15 +135,15 @@ class CArray
|
|
|
135
135
|
"required (got #{data_type_name})"
|
|
136
136
|
end
|
|
137
137
|
if axis.nil?
|
|
138
|
-
return
|
|
138
|
+
return linear_fill_fiber(flatten).reshape(*shape)
|
|
139
139
|
end
|
|
140
140
|
ax = axis
|
|
141
141
|
# A fiber comes back in self's own type (a Face's linear_fetch has already
|
|
142
142
|
# rounded to its grid), so the result assembles in that type and the
|
|
143
143
|
# present cells are never carried through float64.
|
|
144
144
|
out = face? ? template : value.copy
|
|
145
|
-
|
|
146
|
-
out[*key] =
|
|
145
|
+
each_fiber_key(ax) do |key|
|
|
146
|
+
out[*key] = linear_fill_fiber(self[*key])
|
|
147
147
|
end
|
|
148
148
|
out
|
|
149
149
|
end
|
|
@@ -151,13 +151,13 @@ class CArray
|
|
|
151
151
|
# Whether linear interpolation can fill this array: a numeric array, or a
|
|
152
152
|
# Face that defines a linear_fetch of its own -- which is how a Face says it
|
|
153
153
|
# interpolates (the core linear_fetch would read its storage).
|
|
154
|
-
private def
|
|
155
|
-
face? ? self.class.instance_method(:linear_fetch).owner != CArray : numeric?
|
|
154
|
+
private def interpolable?
|
|
155
|
+
face? ? self.class.instance_method(:linear_fetch).owner != CArray : numeric? # type-label: asks what the Face can do, names nothing
|
|
156
156
|
end
|
|
157
157
|
|
|
158
158
|
# 1-D linear-by-index fill of a single (possibly masked) fiber.
|
|
159
|
-
def
|
|
160
|
-
vec.send(:
|
|
159
|
+
def linear_fill_fiber (vec)
|
|
160
|
+
vec.send(:linear_fill_along, CArray.float64(vec.elements) { |i| i.to_f })
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
# Fill the masked cells of this 1-D array by linear interpolation against
|
|
@@ -169,7 +169,7 @@ class CArray
|
|
|
169
169
|
# linear_section answers NaN -- a property of x, not of the values, so a
|
|
170
170
|
# NaN or Inf stored in a present cell is left alone. A Face goes through
|
|
171
171
|
# its own linear_fetch, which keeps its unit and rounds to its grid.
|
|
172
|
-
def
|
|
172
|
+
def linear_fill_along (x)
|
|
173
173
|
present = is_not_masked
|
|
174
174
|
# Fewer than two valid points -> nothing to interpolate between; leave
|
|
175
175
|
# every masked cell masked.
|
|
@@ -180,7 +180,13 @@ class CArray
|
|
|
180
180
|
# lands on its own grid.
|
|
181
181
|
return self[present].linear_fetch(addr)
|
|
182
182
|
end
|
|
183
|
-
|
|
183
|
+
v = value[present]
|
|
184
|
+
# A complex value interpolates its real and imaginary parts.
|
|
185
|
+
interp = v.complex? ? v.linear_fetch(addr) : v.float64.linear_fetch(addr)
|
|
186
|
+
# An integer array takes the nearest integer, half away from zero (as a
|
|
187
|
+
# time Face lands on its own grid), rounded before the store would cut
|
|
188
|
+
# the fraction off.
|
|
189
|
+
interp = interp.round if integer?
|
|
184
190
|
# Built from the values alone, so a fully filled result carries no mask.
|
|
185
191
|
out = value.copy
|
|
186
192
|
masked = is_masked
|
|
@@ -194,7 +200,7 @@ class CArray
|
|
|
194
200
|
# Yield an index key (Array with `nil` at `axis`, integers elsewhere)
|
|
195
201
|
# for every fiber along `axis`. `self[*key]` is that fiber as a masked
|
|
196
202
|
# 1-D view.
|
|
197
|
-
def
|
|
203
|
+
def each_fiber_key (axis)
|
|
198
204
|
outer = shape
|
|
199
205
|
outer_dims = outer.each_index.reject { |k| k == axis }.map { |k| outer[k] }
|
|
200
206
|
idx = Array.new(outer_dims.size, 0)
|
|
@@ -210,7 +216,7 @@ class CArray
|
|
|
210
216
|
end
|
|
211
217
|
end
|
|
212
218
|
end
|
|
213
|
-
private :
|
|
214
|
-
:
|
|
219
|
+
private :hold_along_axis, :gap_fill_linear, :linear_fill_fiber,
|
|
220
|
+
:each_fiber_key
|
|
215
221
|
|
|
216
222
|
end
|
data/lib/carray/meld_reduce.rb
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
#
|
|
17
17
|
# Fast path activates when:
|
|
18
18
|
# - axis kwarg is a single Integer (or absent for flat reduce)
|
|
19
|
-
# - no mask on CAMeld or any parent (
|
|
19
|
+
# - no mask on CAMeld or any parent (skipping masked cells needs per-cell dispatch;
|
|
20
20
|
# the decompose would not see correct mask propagation across parents)
|
|
21
21
|
# - no non-:axis kwargs (min_count / fill_value / keep_axis / etc. punt
|
|
22
22
|
# to super; those change finalisation semantics)
|
|
@@ -66,9 +66,9 @@ class CAMeld
|
|
|
66
66
|
total_count = parents.map(&:elements).inject(:+)
|
|
67
67
|
total_sum / total_count.to_f
|
|
68
68
|
elsif meld_axis_normalized?(axis)
|
|
69
|
-
return super if parents.all? { |p| p.
|
|
69
|
+
return super if parents.all? { |p| p.shape[meld_axis] == 0 }
|
|
70
70
|
total_sum = parents.map { |p| p.sum(axis: axis) }.inject(:+)
|
|
71
|
-
total_count = parents.map { |p| p.
|
|
71
|
+
total_count = parents.map { |p| p.shape[meld_axis] }.inject(:+)
|
|
72
72
|
total_sum / total_count.to_f
|
|
73
73
|
else
|
|
74
74
|
non_meld_axis_decompose(:mean, axis)
|
|
@@ -88,8 +88,8 @@ class CAMeld
|
|
|
88
88
|
return super if parents.all? { |p| p.elements == 0 }
|
|
89
89
|
meld_combine_flat(:min)
|
|
90
90
|
elsif meld_axis_normalized?(axis)
|
|
91
|
-
return super if parents.all? { |p| p.
|
|
92
|
-
nonempty = parents.reject { |p| p.
|
|
91
|
+
return super if parents.all? { |p| p.shape[meld_axis] == 0 }
|
|
92
|
+
nonempty = parents.reject { |p| p.shape[meld_axis] == 0 }
|
|
93
93
|
CArray.stack(nonempty.map { |p| p.min(axis: axis) }).min(axis: 0)
|
|
94
94
|
else
|
|
95
95
|
non_meld_axis_decompose(:min, axis)
|
|
@@ -107,8 +107,8 @@ class CAMeld
|
|
|
107
107
|
return super if parents.all? { |p| p.elements == 0 }
|
|
108
108
|
meld_combine_flat(:max)
|
|
109
109
|
elsif meld_axis_normalized?(axis)
|
|
110
|
-
return super if parents.all? { |p| p.
|
|
111
|
-
nonempty = parents.reject { |p| p.
|
|
110
|
+
return super if parents.all? { |p| p.shape[meld_axis] == 0 }
|
|
111
|
+
nonempty = parents.reject { |p| p.shape[meld_axis] == 0 }
|
|
112
112
|
CArray.stack(nonempty.map { |p| p.max(axis: axis) }).max(axis: 0)
|
|
113
113
|
else
|
|
114
114
|
non_meld_axis_decompose(:max, axis)
|
|
@@ -163,7 +163,7 @@ class CAMeld
|
|
|
163
163
|
private
|
|
164
164
|
|
|
165
165
|
def meld_axis_normalized?(axis)
|
|
166
|
-
(axis
|
|
166
|
+
normalize_axis(axis) == meld_axis
|
|
167
167
|
end
|
|
168
168
|
|
|
169
169
|
# Fast path fires when: no mask anywhere, no non-:axis kwargs, and axis
|
|
@@ -177,9 +177,7 @@ class CAMeld
|
|
|
177
177
|
return false unless (kw.keys - [:axis]).empty?
|
|
178
178
|
axis = kw[:axis]
|
|
179
179
|
unless axis.nil?
|
|
180
|
-
return false unless axis.is_a?(Integer)
|
|
181
|
-
norm = axis < 0 ? axis + ndim : axis
|
|
182
|
-
return false unless norm >= 0 && norm < ndim
|
|
180
|
+
return false unless axis.is_a?(Integer) && axis >= -ndim && axis < ndim # axis-check: a gate only; super normalizes
|
|
183
181
|
end
|
|
184
182
|
return false if has_mask?
|
|
185
183
|
parents.each { |p| return false if p.has_mask? }
|
|
@@ -218,7 +216,7 @@ class CAMeld
|
|
|
218
216
|
# meld_axis branch and never reaches here.)
|
|
219
217
|
def non_meld_axis_decompose(op, axis)
|
|
220
218
|
parts = parents.map { |p| p.public_send(op, axis: axis) }
|
|
221
|
-
axis_norm = axis
|
|
219
|
+
axis_norm = normalize_axis(axis)
|
|
222
220
|
new_meld_axis = axis_norm < meld_axis ? meld_axis - 1 : meld_axis
|
|
223
221
|
CArray.meld(parts, axis: new_meld_axis).copy
|
|
224
222
|
end
|
|
@@ -241,7 +239,7 @@ class CAMeld
|
|
|
241
239
|
return yield if parents.any? { |p| p.elements < min_n }
|
|
242
240
|
finalize_scalar(welford_flat, sample, sqrt)
|
|
243
241
|
elsif meld_axis_normalized?(axis)
|
|
244
|
-
return yield if parents.any? { |p| p.
|
|
242
|
+
return yield if parents.any? { |p| p.shape[meld_axis] < min_n }
|
|
245
243
|
finalize_vector(welford_axis(axis), sample, sqrt)
|
|
246
244
|
else
|
|
247
245
|
# Non-meld-axis: each parent's variance/stddev is independent along
|
|
@@ -288,7 +286,7 @@ class CAMeld
|
|
|
288
286
|
mean = nil
|
|
289
287
|
m2 = nil
|
|
290
288
|
parents.each do |p|
|
|
291
|
-
np = p.
|
|
289
|
+
np = p.shape[meld_axis]
|
|
292
290
|
mp = p.mean(axis: axis)
|
|
293
291
|
# variance is undefined at n=1; for variancep with n=1, m2_p = 0 by
|
|
294
292
|
# definition (variance_family already gated n >= min_n).
|
data/lib/carray/methods/bin.rb
CHANGED
|
@@ -18,6 +18,11 @@ class CArray
|
|
|
18
18
|
# that side. NaN / masked input cells are always masked in the
|
|
19
19
|
# output, independently of `lfill` / `ufill`.
|
|
20
20
|
#
|
|
21
|
+
# When `vmin == vmax` the range is one point: a value equal to it is
|
|
22
|
+
# in bin 0 whatever `bins:` or `step` says, and a value below or above
|
|
23
|
+
# it is out of range. An array whose values are all the same is
|
|
24
|
+
# binned this way by `bin(a.min, a.max, ...)`.
|
|
25
|
+
#
|
|
21
26
|
# Use `snap(step, offset:)` when the desired output is the snapped
|
|
22
27
|
# value; use `bin_to(edges)` when the edges are non-uniform.
|
|
23
28
|
#
|
|
@@ -46,23 +51,21 @@ class CArray
|
|
|
46
51
|
end
|
|
47
52
|
raise ArgumentError, "bin: vmin > vmax" if vmin > vmax
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
unless bins.nil? || bins.is_a?(Integer)
|
|
55
|
+
raise TypeError, "bin: bins: must be an Integer (got #{bins.inspect})"
|
|
56
|
+
end
|
|
52
57
|
if vmin == vmax
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if inv.count(true) > 0
|
|
60
|
-
out.mask = out.has_mask? ? (out.mask | inv) : inv
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
return out
|
|
58
|
+
# A range of one point: a value equal to it is in bin 0 whatever the
|
|
59
|
+
# count of bins (or the step), a value below or above it is out of
|
|
60
|
+
# range.
|
|
61
|
+
raise ArgumentError, "bin: n must be >= 1" if bins && bins < 1
|
|
62
|
+
return bin_to(CArray.float64(2) { vmin.to_f },
|
|
63
|
+
lfill: lfill, ufill: ufill, include_max: true)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
n = bins || ((vmax - vmin).to_f / step).round
|
|
67
|
+
raise ArgumentError, "bin: n must be >= 1" if n < 1
|
|
68
|
+
|
|
66
69
|
# Delegate to `bin_to` with generated uniform edges — same kernel
|
|
67
70
|
# (`histbin_ki`) as `histogram`, so semantics are identical.
|
|
68
71
|
edges = CArray.float64(n + 1).span(vmin..vmax)
|
|
@@ -99,8 +102,9 @@ class CArray
|
|
|
99
102
|
# @return [CArray] `CA_INT64` array with the same shape as `self`
|
|
100
103
|
# holding bin indices in `[0, N-1]` (or the fill values / mask
|
|
101
104
|
# for out-of-range and masked cells).
|
|
102
|
-
# @raise [ArgumentError] when `edges` is not 1-D
|
|
103
|
-
#
|
|
105
|
+
# @raise [ArgumentError] when `edges` is not 1-D, has fewer than 2
|
|
106
|
+
# values, has a masked value or NaN, or decreases anywhere. Equal
|
|
107
|
+
# edges (a bin of zero width) and infinite ones are accepted.
|
|
104
108
|
# @example
|
|
105
109
|
# e = CA_FLOAT64([0, 1, 10, 100])
|
|
106
110
|
# v = CA_FLOAT64([0.5, 5.0, 50.0, -1.0, 200.0])
|
|
@@ -120,8 +124,12 @@ class CArray
|
|
|
120
124
|
# under -> -1, in-range -> 0..N-1, over -> N.
|
|
121
125
|
out = src.send(:histbin_ki, e, include_max) - 1
|
|
122
126
|
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
# Both sides are found before either is written, so a fill that equals
|
|
128
|
+
# the other side's index (lfill: n) is not taken for it.
|
|
129
|
+
under = out.eq(-1)
|
|
130
|
+
over = out.eq(n)
|
|
131
|
+
out[under] = lfill.nil? ? UNDEF : lfill
|
|
132
|
+
out[over] = ufill.nil? ? UNDEF : ufill
|
|
125
133
|
out
|
|
126
134
|
end
|
|
127
135
|
|
|
@@ -35,6 +35,22 @@ class CArray
|
|
|
35
35
|
raise CArray::DataTypeError,
|
|
36
36
|
"bincount requires an integer label array (got #{data_type_name})"
|
|
37
37
|
end
|
|
38
|
+
unless length.is_a?(Integer)
|
|
39
|
+
raise TypeError, "bincount: length: must be an Integer (got #{length.inspect})"
|
|
40
|
+
end
|
|
41
|
+
if length < 0
|
|
42
|
+
raise ArgumentError, "bincount: length: must not be negative (got #{length})"
|
|
43
|
+
end
|
|
44
|
+
unless weights.nil?
|
|
45
|
+
unless weights.is_a?(CArray)
|
|
46
|
+
raise ArgumentError, "bincount: weights must be a CArray"
|
|
47
|
+
end
|
|
48
|
+
unless weights.integer? || weights.float?
|
|
49
|
+
raise CArray::DataTypeError,
|
|
50
|
+
"bincount: weights must be numeric (got " \
|
|
51
|
+
"#{CArray::Inspector.type_label(weights)})"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
38
54
|
|
|
39
55
|
if elements.zero?
|
|
40
56
|
if weights
|
|
@@ -69,7 +69,7 @@ class CArray
|
|
|
69
69
|
raise ArgumentError,
|
|
70
70
|
"pack_bits: expected CA_BOOLEAN / CA_UINT8 / CA_INT8 (got #{data_type_name})"
|
|
71
71
|
end
|
|
72
|
-
raise ArgumentError, "pack_bits: 1-D CArray expected (got
|
|
72
|
+
raise ArgumentError, "pack_bits: 1-D CArray expected (got ndim #{ndim})" unless ndim == 1
|
|
73
73
|
n = elements
|
|
74
74
|
n_bytes = (n + 7) / 8
|
|
75
75
|
packed = CArray.uint8(n_bytes) { 0 }
|
|
@@ -6,7 +6,8 @@ class CArray
|
|
|
6
6
|
#
|
|
7
7
|
# Where `self == i`, the result takes `choices[i]` -- a scalar
|
|
8
8
|
# fills those cells, a CArray contributes its corresponding
|
|
9
|
-
# cells. The result has the same shape as `self`.
|
|
9
|
+
# cells. The result has the same shape as `self`. Where a label
|
|
10
|
+
# is masked, the result is masked.
|
|
10
11
|
#
|
|
11
12
|
# @param choices [Array<CArray, Object>] values indexed by the
|
|
12
13
|
# labels in `self`; each entry is either a same-shape CArray
|
|
@@ -15,7 +16,10 @@ class CArray
|
|
|
15
16
|
# When `nil` it is inferred: `CArray.result_type` of the CArray
|
|
16
17
|
# choices, or `CA_OBJECT` when every choice is a scalar.
|
|
17
18
|
# @return [CArray] new CArray with the shape of `self` holding
|
|
18
|
-
# the chosen values
|
|
19
|
+
# the chosen values; a {CAString} when every choice is a String
|
|
20
|
+
# or a {CAString} and `data_type` is not given.
|
|
21
|
+
# @raise [IndexError] if a label that is not masked is negative or
|
|
22
|
+
# not less than the number of choices.
|
|
19
23
|
# @example
|
|
20
24
|
# ref = CA_INT([[0, 1, 2], [1, 2, 0], [2, 0, 1]])
|
|
21
25
|
# a = CArray.int(3, 3).seq(1)
|
|
@@ -24,6 +28,14 @@ class CArray
|
|
|
24
28
|
# ref.choose([a, b, c]) # per-cell pick from a / b / c
|
|
25
29
|
# ref.choose(["a", "b", "c"]) # recode labels to values
|
|
26
30
|
def choose (choices, data_type: nil)
|
|
31
|
+
outside = (self.lt(0) | self.ge(choices.size)).where
|
|
32
|
+
if outside.elements > 0
|
|
33
|
+
addr = outside[0]
|
|
34
|
+
raise IndexError,
|
|
35
|
+
"choose: label #{self[addr]} at address #{addr} is out of range " \
|
|
36
|
+
"for #{choices.size} choices"
|
|
37
|
+
end
|
|
38
|
+
text = !data_type && CArray.send(:selection_text?, choices)
|
|
27
39
|
unless data_type
|
|
28
40
|
ca = choices.select { |v| v.is_a?(CArray) }
|
|
29
41
|
data_type = ca.empty? ? CA_OBJECT : CArray.result_type(*ca)
|
|
@@ -33,7 +45,8 @@ class CArray
|
|
|
33
45
|
s = self.eq(i)
|
|
34
46
|
out[s] = v.is_a?(CArray) ? v[s] : v
|
|
35
47
|
end
|
|
36
|
-
out
|
|
48
|
+
out[self.is_masked] = UNDEF if has_mask?
|
|
49
|
+
text ? CAString.wrap(out) : out
|
|
37
50
|
end
|
|
38
51
|
|
|
39
52
|
end
|
|
@@ -139,6 +139,7 @@ class CArray
|
|
|
139
139
|
# per-piece dim sizes summed along each tile axis (= block-matrix
|
|
140
140
|
# consistency required across rows / columns).
|
|
141
141
|
def self.__ragged_paste (list, tdim, axis, data_type, name)
|
|
142
|
+
shared_attrs = attrs_shared_by(list)
|
|
142
143
|
list = CArray.promote_list(list, data_type: data_type)
|
|
143
144
|
# promote_list has already enforced homogeneity (= common data_type, and
|
|
144
145
|
# for Face elements: same Face class + portable + state-compatible). So,
|
|
@@ -150,7 +151,7 @@ class CArray
|
|
|
150
151
|
# its cell width, and the result is as wide as its widest piece.
|
|
151
152
|
face_parent = list[0].face? ? list.max_by(&:bytes) : nil
|
|
152
153
|
if face_parent
|
|
153
|
-
list = list.map { |x| x.__send__(:
|
|
154
|
+
list = list.map { |x| x.__send__(:face_storage) }
|
|
154
155
|
end
|
|
155
156
|
data_type = list[0].data_type
|
|
156
157
|
# Fixlen pieces of different widths join at the widest, so no cell is
|
|
@@ -223,6 +224,7 @@ class CArray
|
|
|
223
224
|
obj.paste(idx, item)
|
|
224
225
|
end
|
|
225
226
|
obj = obj.face_lift(face_parent) if face_parent
|
|
227
|
+
obj.set_attrs(shared_attrs) unless shared_attrs.empty?
|
|
226
228
|
obj
|
|
227
229
|
end
|
|
228
230
|
private_class_method :__ragged_paste
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
# Treats the trailing K axes of `indices.shape = (..., K)` as coordinate tuples
|
|
6
6
|
# and consumes the first K axes of params while carrying the remaining axes (= rest).
|
|
7
7
|
#
|
|
8
|
-
# Implementation goes through
|
|
8
|
+
# Implementation goes through an index view (CAGrid) of the flattened array:
|
|
9
9
|
# `params.flatten[flat_addr]`. Pure Ruby, no additional framework, composed
|
|
10
|
-
# entirely from existing indexers as in `take_along_axis`. Since
|
|
10
|
+
# entirely from existing indexers as in `take_along_axis`. Since the view is
|
|
11
11
|
# writable, write-through via `put_nd` uses last-write-wins semantics (= last
|
|
12
12
|
# value wins on duplicate indices, same contract as `put_along_axis`). For
|
|
13
13
|
# accumulate semantics, route to the `scatter_*!` family instead.
|
|
@@ -63,7 +63,7 @@ class CArray
|
|
|
63
63
|
def gather_nd (indices)
|
|
64
64
|
flat_addr, outer, rest = gather_nd_flat_addr(indices, "gather_nd")
|
|
65
65
|
out_shape = outer + rest
|
|
66
|
-
# flatten + 1-D fancy indexing ->
|
|
66
|
+
# flatten + 1-D fancy indexing -> CAGrid view -> materialise via .copy.
|
|
67
67
|
# A masked coordinate reads as UNDEF: project carries the address mask.
|
|
68
68
|
result = flat_addr.has_mask? ? flatten.project(flat_addr) : self.flatten[flat_addr].copy
|
|
69
69
|
out_shape.empty? ? result : result.reshape(*out_shape)
|
|
@@ -91,10 +91,19 @@ class CArray
|
|
|
91
91
|
# @raise [ArgumentError] on the same conditions as {#gather_nd}.
|
|
92
92
|
# @raise [IndexError] when a coordinate is out of range on any axis.
|
|
93
93
|
def put_nd (indices, values)
|
|
94
|
+
# The write goes through a flattened view, which carries a frozen
|
|
95
|
+
# array's read-only flag but not its frozenness; say what the array is.
|
|
96
|
+
raise FrozenError.new("can't modify frozen #{self.class}", receiver: self) if frozen?
|
|
94
97
|
flat_addr, _outer, _rest = gather_nd_flat_addr(indices, "put_nd")
|
|
95
98
|
if flat_addr.has_mask?
|
|
96
|
-
# A masked coordinate writes nothing
|
|
97
|
-
|
|
99
|
+
# A masked coordinate writes nothing. The values are laid out over
|
|
100
|
+
# the coordinates by the store itself, so they mean what they mean
|
|
101
|
+
# unmasked, and only the cells at unmasked coordinates are written.
|
|
102
|
+
flat = flatten
|
|
103
|
+
vals = flat.project(flat_addr)
|
|
104
|
+
vals[] = values
|
|
105
|
+
sel = flat_addr.is_not_masked
|
|
106
|
+
flat[flat_addr[sel]] = vals[sel]
|
|
98
107
|
else
|
|
99
108
|
self.flatten[flat_addr] = values
|
|
100
109
|
end
|
|
@@ -43,13 +43,13 @@ class CArray
|
|
|
43
43
|
# The original cells are copied as storage, so a read-only Face inserts
|
|
44
44
|
# too; the block's value is written as a value of the array, which a
|
|
45
45
|
# read-only Face refuses.
|
|
46
|
-
out =
|
|
46
|
+
out = blank_like(newdim)
|
|
47
47
|
if block_given?
|
|
48
48
|
sel = CArray.boolean(*newdim) { true }
|
|
49
49
|
sel[*grids] = false
|
|
50
50
|
out[sel] = block.call
|
|
51
51
|
end
|
|
52
|
-
out.__send__(:
|
|
52
|
+
out.__send__(:face_storage)[*grids] = face_storage
|
|
53
53
|
return out
|
|
54
54
|
end
|
|
55
55
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @!group Elementwise math
|
|
4
|
+
|
|
5
|
+
# @overload is_between(lo, hi, exclude: nil)
|
|
6
|
+
# @overload is_between(range)
|
|
7
|
+
# Returns a boolean CArray of the same shape as `self`, `true` at each
|
|
8
|
+
# cell whose value lies between `lo` and `hi`. Both bounds are included
|
|
9
|
+
# unless `exclude:` names the one to leave out:
|
|
10
|
+
#
|
|
11
|
+
# a.is_between(2, 4) # 2 <= a <= 4
|
|
12
|
+
# a.is_between(2, 4, exclude: :upper) # 2 <= a < 4
|
|
13
|
+
# a.is_between(2, 4, exclude: :lower) # 2 < a <= 4
|
|
14
|
+
# a.is_between(2, 4, exclude: :both) # 2 < a < 4
|
|
15
|
+
#
|
|
16
|
+
# A Range says the same as the two bounds, its excluded end as
|
|
17
|
+
# `exclude: :upper`: `a.is_between(2...4)`. A bound given as `nil` (or
|
|
18
|
+
# the open end of `..4` / `2..`) leaves that side unbounded.
|
|
19
|
+
#
|
|
20
|
+
# It is the two comparisons joined by `&`, so it takes what they take --
|
|
21
|
+
# a time array is bounded by Strings, `Time` or elements -- and answers as
|
|
22
|
+
# they do: a masked cell is masked, and NaN is outside every interval.
|
|
23
|
+
# Bounds the wrong way round (`lo > hi`) contain nothing.
|
|
24
|
+
# @param lo [Object, nil] the lower bound.
|
|
25
|
+
# @param hi [Object, nil] the upper bound.
|
|
26
|
+
# @param exclude [nil, :lower, :upper, :both] the bounds left out.
|
|
27
|
+
# @return [CArray] boolean, the shape of `self`.
|
|
28
|
+
# @raise [ArgumentError] when neither bound is given, `exclude:` is not one
|
|
29
|
+
# of the three, or a Range comes with a second bound or `exclude:`.
|
|
30
|
+
def is_between (*bounds, exclude: nil)
|
|
31
|
+
if bounds.size == 1 && bounds[0].is_a?(Range)
|
|
32
|
+
unless exclude.nil?
|
|
33
|
+
raise ArgumentError,
|
|
34
|
+
"is_between: a Range says which ends it includes; give two bounds to use exclude:"
|
|
35
|
+
end
|
|
36
|
+
range = bounds[0]
|
|
37
|
+
lo, hi = range.begin, range.end
|
|
38
|
+
exclude = :upper if range.exclude_end? && !hi.nil?
|
|
39
|
+
elsif bounds.size == 2
|
|
40
|
+
lo, hi = bounds
|
|
41
|
+
else
|
|
42
|
+
raise ArgumentError,
|
|
43
|
+
"is_between: give a lower and an upper bound, or a Range (got #{bounds.size} arguments)"
|
|
44
|
+
end
|
|
45
|
+
unless [nil, :lower, :upper, :both].include?(exclude)
|
|
46
|
+
raise ArgumentError,
|
|
47
|
+
"is_between: exclude: must be :lower, :upper or :both (got #{exclude.inspect})"
|
|
48
|
+
end
|
|
49
|
+
if lo.nil? && hi.nil?
|
|
50
|
+
raise ArgumentError, "is_between: give at least one bound"
|
|
51
|
+
end
|
|
52
|
+
lower = lo.nil? ? nil : (%i[lower both].include?(exclude) ? gt(lo) : ge(lo))
|
|
53
|
+
upper = hi.nil? ? nil : (%i[upper both].include?(exclude) ? lt(hi) : le(hi))
|
|
54
|
+
lower && upper ? lower & upper : (lower || upper)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @!endgroup
|
|
58
|
+
|
|
59
|
+
end
|