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
|
@@ -41,7 +41,7 @@ class CASegmentIterator < CAIterator
|
|
|
41
41
|
kw = {}
|
|
42
42
|
kw[:offsets] = offsets unless offsets.nil?
|
|
43
43
|
kw[:lengths] = lengths unless lengths.nil?
|
|
44
|
-
bounds = CArray.
|
|
44
|
+
bounds = CArray.__segment_boundaries_from_lengths_or_offsets__("segments", **kw)
|
|
45
45
|
origin, finish = bounds[0], bounds[-1]
|
|
46
46
|
if origin < 0 || finish > value.elements
|
|
47
47
|
raise ArgumentError,
|
|
@@ -58,8 +58,8 @@ class CASegmentIterator < CAIterator
|
|
|
58
58
|
@offsets = @bounds[0...-1].copy # the starts, for the kernels
|
|
59
59
|
@elements = @k > 0 ? (@bounds[1..-1] - @bounds[0...-1]) : CArray.int64(0)
|
|
60
60
|
@grouped = finish > origin ? value.reshape(value.elements)[origin...finish].copy
|
|
61
|
-
:
|
|
62
|
-
@empty =
|
|
61
|
+
: value.reshape(value.elements)[0...0].copy
|
|
62
|
+
@empty = @grouped[0...0] # an empty segment is the values' own kind
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# @overload inspect
|
|
@@ -153,7 +153,7 @@ class CASegmentIterator < CAIterator
|
|
|
153
153
|
def sum(axis: nil)
|
|
154
154
|
return axis_sum(axis) if axis
|
|
155
155
|
m = moments
|
|
156
|
-
return per_segment(
|
|
156
|
+
return per_segment(core_reduce_kind(:sum)) { |s| s.sum } unless m
|
|
157
157
|
m[:sum].copy # the moments sum IS the core fold (empty -> 0.0)
|
|
158
158
|
end
|
|
159
159
|
|
|
@@ -167,7 +167,7 @@ class CASegmentIterator < CAIterator
|
|
|
167
167
|
# @return [CArray]
|
|
168
168
|
def accumulate(axis: nil)
|
|
169
169
|
return axis_by_masked_copy(axis, :accumulate, core_reduce_type(:accumulate)) if axis
|
|
170
|
-
per_segment(
|
|
170
|
+
per_segment(core_reduce_kind(:accumulate)) { |s| s.accumulate }
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
# @overload max
|
|
@@ -177,7 +177,7 @@ class CASegmentIterator < CAIterator
|
|
|
177
177
|
def max(axis: nil)
|
|
178
178
|
return axis_moments(axis, :max)[:max] if axis
|
|
179
179
|
m = moments
|
|
180
|
-
m ? m[:max].copy : per_segment(
|
|
180
|
+
m ? m[:max].copy : per_segment(core_reduce_kind(:max)) { |s| s.max }
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
# @overload min
|
|
@@ -187,7 +187,7 @@ class CASegmentIterator < CAIterator
|
|
|
187
187
|
def min(axis: nil)
|
|
188
188
|
return axis_moments(axis, :min)[:min] if axis
|
|
189
189
|
m = moments
|
|
190
|
-
m ? m[:min].copy : per_segment(
|
|
190
|
+
m ? m[:min].copy : per_segment(core_reduce_kind(:min)) { |s| s.min }
|
|
191
191
|
end
|
|
192
192
|
|
|
193
193
|
# @overload mean
|
|
@@ -196,7 +196,7 @@ class CASegmentIterator < CAIterator
|
|
|
196
196
|
def mean(axis: nil)
|
|
197
197
|
return axis_mean(axis) if axis
|
|
198
198
|
m = moments
|
|
199
|
-
return per_segment(
|
|
199
|
+
return per_segment(core_reduce_kind(:mean)) { |s| s.mean } unless m
|
|
200
200
|
cnt = m[:count]
|
|
201
201
|
out = m[:sum] / cnt.float64 # count 0 -> NaN, masked next
|
|
202
202
|
out[cnt.eq(0)] = UNDEF # empty / all-masked category -> MASKED
|
|
@@ -220,8 +220,8 @@ class CASegmentIterator < CAIterator
|
|
|
220
220
|
# @return [CArray]
|
|
221
221
|
def percentile (p, axis: nil)
|
|
222
222
|
return axis_order_stat(axis, :percentile, p) if axis
|
|
223
|
-
unless
|
|
224
|
-
return per_segment(
|
|
223
|
+
unless kernel_reads?
|
|
224
|
+
return per_segment(core_reduce_kind(:percentile, p)) { |s| s.percentile(p) }
|
|
225
225
|
end
|
|
226
226
|
out = CArray.float64(@k)
|
|
227
227
|
grouped.send(:__reduceat_percentile__, @offsets, p.to_f, out)
|
|
@@ -235,8 +235,8 @@ class CASegmentIterator < CAIterator
|
|
|
235
235
|
# MASKED. For a single fraction q in 0..1 use `percentile(q * 100)`.
|
|
236
236
|
# @return [Array<CArray>]
|
|
237
237
|
def quantile
|
|
238
|
-
unless
|
|
239
|
-
return CArray.
|
|
238
|
+
unless kernel_reads?
|
|
239
|
+
return CArray.__name_errors_after_method__(:quantile) { [0, 25, 50, 75, 100].map { |p| percentile(p) } }
|
|
240
240
|
end
|
|
241
241
|
outs = Array.new(5) { CArray.float64(@k) }
|
|
242
242
|
grouped.send(:__reduceat_quantile__, @offsets, *outs)
|
|
@@ -251,7 +251,7 @@ class CASegmentIterator < CAIterator
|
|
|
251
251
|
def variance(axis: nil)
|
|
252
252
|
return axis_by_masked_copy(axis, :variance) if axis
|
|
253
253
|
m = moments
|
|
254
|
-
return per_segment(
|
|
254
|
+
return per_segment(core_reduce_kind(:variance)) { |s| s.variance } unless m
|
|
255
255
|
cnt = m[:count]
|
|
256
256
|
means = m[:sum] / cnt.float64 # per-segment mean (garbage where count 0/1,
|
|
257
257
|
out = CArray.float64(@k) # ignored by the kernel's n<2 guards)
|
|
@@ -267,7 +267,7 @@ class CASegmentIterator < CAIterator
|
|
|
267
267
|
def stddev(axis: nil)
|
|
268
268
|
return axis_by_masked_copy(axis, :stddev) if axis
|
|
269
269
|
m = moments
|
|
270
|
-
return per_segment(
|
|
270
|
+
return per_segment(core_reduce_kind(:stddev)) { |s| s.stddev } unless m
|
|
271
271
|
variance.sqrt # sqrt propagates the n<2 mask
|
|
272
272
|
end
|
|
273
273
|
|
|
@@ -278,7 +278,7 @@ class CASegmentIterator < CAIterator
|
|
|
278
278
|
# @return [CArray]
|
|
279
279
|
def prod(axis: nil)
|
|
280
280
|
return axis_prod(axis) if axis
|
|
281
|
-
return per_segment(
|
|
281
|
+
return per_segment(core_reduce_kind(:prod)) { |s| s.prod } unless kernel_reads?
|
|
282
282
|
out = CArray.float64(@k)
|
|
283
283
|
grouped.send(:__reduceat_prod__, @offsets, out)
|
|
284
284
|
out
|
|
@@ -304,6 +304,48 @@ class CASegmentIterator < CAIterator
|
|
|
304
304
|
aa ? aa[:any] : per_segment(CA_BOOLEAN) { |s| s.any }
|
|
305
305
|
end
|
|
306
306
|
|
|
307
|
+
# @overload first
|
|
308
|
+
# Returns the first present value of each segment, in the value's data
|
|
309
|
+
# type (matching `CArray#first`). A segment with no present value is
|
|
310
|
+
# UNDEF.
|
|
311
|
+
# @return [CArray]
|
|
312
|
+
def first (axis: nil)
|
|
313
|
+
return axis_by_core(axis, :first) if axis
|
|
314
|
+
edge_values(:first)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# @overload last
|
|
318
|
+
# Returns the last present value of each segment, in the value's data
|
|
319
|
+
# type (matching `CArray#last`). A segment with no present value is
|
|
320
|
+
# UNDEF.
|
|
321
|
+
# @return [CArray]
|
|
322
|
+
def last (axis: nil)
|
|
323
|
+
return axis_by_core(axis, :last) if axis
|
|
324
|
+
edge_values(:last)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# @overload nunique
|
|
328
|
+
# Returns the number of distinct present values in each segment as int64,
|
|
329
|
+
# with the distinctness of `CArray#nunique` (every NaN is one value,
|
|
330
|
+
# -0.0 is 0.0). A segment with no present value counts 0.
|
|
331
|
+
# @return [CArray]
|
|
332
|
+
def nunique (axis: nil)
|
|
333
|
+
return axis_by_core(axis, :nunique) if axis
|
|
334
|
+
return per_segment(CA_INT64) { |s| s.nunique } unless CArray.__fast_paths_enabled__
|
|
335
|
+
out = CArray.int64(@k)
|
|
336
|
+
return out if grouped.elements.zero?
|
|
337
|
+
# Each cell's value becomes a code, so a distinct (segment, value) pair is
|
|
338
|
+
# a distinct int64 key; the keys' segments, counted, are the answer.
|
|
339
|
+
values = grouped.categorize
|
|
340
|
+
width = [values.labels.size, 1].max
|
|
341
|
+
keys = CArray.segment_index(offsets: @bounds) * width + values.codes.int64
|
|
342
|
+
owners = keys.unique / width
|
|
343
|
+
return out if owners.elements.zero?
|
|
344
|
+
counts = owners.bincount
|
|
345
|
+
out[0...counts.elements] = counts
|
|
346
|
+
out
|
|
347
|
+
end
|
|
348
|
+
|
|
307
349
|
# ---- tier 2 (fused / population / position) ------------------------------
|
|
308
350
|
|
|
309
351
|
# @overload minmax
|
|
@@ -324,17 +366,19 @@ class CASegmentIterator < CAIterator
|
|
|
324
366
|
|
|
325
367
|
# @overload variancep
|
|
326
368
|
# Per-segment POPULATION variance (ddof=0) as float64, matching
|
|
327
|
-
# `CArray#variancep`: empty / all-masked -> MASKED, single value -> 0.0
|
|
369
|
+
# `CArray#variancep`: empty / all-masked -> MASKED, single value -> 0.0
|
|
370
|
+
# (NaN when that value is a NaN or an infinity).
|
|
328
371
|
# Derived from the sample variance (variancep = variance * (n-1) / n), so it
|
|
329
372
|
# reuses the centred two-pass kernel with no extra walk.
|
|
330
373
|
# @return [CArray]
|
|
331
374
|
def variancep(axis: nil)
|
|
332
375
|
return axis_by_masked_copy(axis, :variancep) if axis
|
|
333
376
|
m = moments
|
|
334
|
-
return per_segment(
|
|
377
|
+
return per_segment(core_reduce_kind(:variancep)) { |s| s.variancep } unless m
|
|
335
378
|
cnt = m[:count]
|
|
336
379
|
vp = variance * (cnt - 1).float64 / cnt.float64
|
|
337
|
-
|
|
380
|
+
one = cnt.eq(1) # one value: defined, unlike variance --
|
|
381
|
+
vp[one] = (m[:sum] - m[:sum])[one] # 0.0, or NaN for a NaN or an infinity
|
|
338
382
|
vp
|
|
339
383
|
end
|
|
340
384
|
|
|
@@ -344,7 +388,7 @@ class CASegmentIterator < CAIterator
|
|
|
344
388
|
def stddevp(axis: nil)
|
|
345
389
|
return axis_by_masked_copy(axis, :stddevp) if axis
|
|
346
390
|
m = moments
|
|
347
|
-
return per_segment(
|
|
391
|
+
return per_segment(core_reduce_kind(:stddevp)) { |s| s.stddevp } unless m
|
|
348
392
|
variancep.sqrt
|
|
349
393
|
end
|
|
350
394
|
|
|
@@ -421,7 +465,7 @@ class CASegmentIterator < CAIterator
|
|
|
421
465
|
# @return [CArray]
|
|
422
466
|
def wsum (weights, axis: nil)
|
|
423
467
|
return axis_wsum_wmean(weights, axis, :wsum)[0] if axis
|
|
424
|
-
kernel =
|
|
468
|
+
kernel = kernel_reads?
|
|
425
469
|
wg = scatter_weights(weights, kernel)
|
|
426
470
|
return kernel_weighted(wg)[0] if kernel
|
|
427
471
|
fold_weighted(:wsum, wg, 0.0) { |v, ws| v.wsum(ws) }
|
|
@@ -435,7 +479,7 @@ class CASegmentIterator < CAIterator
|
|
|
435
479
|
# @return [CArray]
|
|
436
480
|
def wmean (weights, axis: nil)
|
|
437
481
|
return axis_wsum_wmean(weights, axis, :wmean)[1] if axis
|
|
438
|
-
kernel =
|
|
482
|
+
kernel = kernel_reads?
|
|
439
483
|
wg = scatter_weights(weights, kernel)
|
|
440
484
|
return kernel_weighted(wg)[1] if kernel
|
|
441
485
|
fold_weighted(:wmean, wg, UNDEF) { |v, ws| v.wmean(ws) }
|
|
@@ -507,7 +551,7 @@ class CASegmentIterator < CAIterator
|
|
|
507
551
|
# data type is the core's (`cumsum` of a boolean is uint64, of an integer
|
|
508
552
|
# float64), a Face keeps its Face (`cummin` of a time column is a time), and
|
|
509
553
|
# a scan the core refuses (`cumsum` of a time column) raises in the core's
|
|
510
|
-
# words. When the fused C kernel
|
|
554
|
+
# words. When the fused C kernel __scan_by_axis_groups__ answers in that data
|
|
511
555
|
# type, it runs with the whole source as a single grouped axis and the
|
|
512
556
|
# segment of each cell (#codes) as the single bundle, emitting in source
|
|
513
557
|
# order; otherwise each segment is scanned by the core and scattered back.
|
|
@@ -527,8 +571,122 @@ class CASegmentIterator < CAIterator
|
|
|
527
571
|
# @!method cumcount
|
|
528
572
|
# Per-segment 1-based within-segment ordinal (int64), source-shaped.
|
|
529
573
|
# @return [CArray]
|
|
530
|
-
|
|
531
|
-
|
|
574
|
+
CArray.send(:define_named_methods, self,
|
|
575
|
+
[:cumsum, :cumprod, :cummax, :cummin, :cumcount], :segment_scan)
|
|
576
|
+
|
|
577
|
+
private def segment_scan (op, axis: nil)
|
|
578
|
+
axis ? axis_scan(op, axis) : scan(op)
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# @overload lag(n = 1)
|
|
582
|
+
# Returns the value `n` steps earlier within each segment, source-shaped,
|
|
583
|
+
# as `CArray#lag` does along an axis. A cell whose segment has no value
|
|
584
|
+
# `n` steps back is UNDEF, so a lag never reaches into the segment before;
|
|
585
|
+
# a cell in no segment is UNDEF. A negative `n` looks ahead.
|
|
586
|
+
# @param n [Integer]
|
|
587
|
+
# @return [CArray]
|
|
588
|
+
def lag (n = 1, axis: nil)
|
|
589
|
+
unless n.is_a?(Integer)
|
|
590
|
+
raise TypeError, "lag: n must be an Integer (got #{n.class})"
|
|
591
|
+
end
|
|
592
|
+
return axis_lag(n, axis) if axis
|
|
593
|
+
out = blank_like(@value, @src_shape)
|
|
594
|
+
out[] = UNDEF
|
|
595
|
+
m = grouped.elements
|
|
596
|
+
return out if m.zero?
|
|
597
|
+
# The copy is in segment order, so one shift moves every segment; the
|
|
598
|
+
# cells it brought across a segment boundary are then cut.
|
|
599
|
+
lagged = grouped.lag(n)
|
|
600
|
+
lagged = out.data_type == CA_OBJECT && lagged.face? ?
|
|
601
|
+
CArray.object(m) { lagged.to_a } : lagged.copy
|
|
602
|
+
unless n.zero?
|
|
603
|
+
seg = CArray.segment_index(offsets: @bounds)
|
|
604
|
+
start = @bounds[seg]
|
|
605
|
+
pos = CArray.int64(m).seq! - start
|
|
606
|
+
lagged[n > 0 ? pos.lt(n) : pos.ge(@bounds[seg + 1] - start + n)] = UNDEF
|
|
607
|
+
end
|
|
608
|
+
out.reshape(@value.elements)[perm] = lagged
|
|
609
|
+
out
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# @overload strip_mask(method:)
|
|
613
|
+
# Returns the values with each masked cell filled from its segment, as
|
|
614
|
+
# `CArray#strip_mask(method:)` fills a 1-D array: `:forward` (`:ffill`)
|
|
615
|
+
# carries the segment's last present value forward, `:backward`
|
|
616
|
+
# (`:bfill`) its next one back. A fill never reaches across into another
|
|
617
|
+
# segment, so a masked cell with nothing to carry in its segment stays
|
|
618
|
+
# masked; a cell in no segment is UNDEF. The value is not modified.
|
|
619
|
+
# @param method [Symbol] `:forward`, `:ffill`, `:backward` or `:bfill`.
|
|
620
|
+
# @return [CArray] source-shaped
|
|
621
|
+
def strip_mask (method:, axis: nil)
|
|
622
|
+
backward = CAIterator.hold_backward?(method)
|
|
623
|
+
return axis_hold(method, axis) if axis
|
|
624
|
+
if @value.face? && ! face_output?
|
|
625
|
+
return scatter_segments(->(shape) { blank_like(@value, shape) }) { |s| s.strip_mask(method: method) }
|
|
626
|
+
end
|
|
627
|
+
out = blank_like(@value, @src_shape)
|
|
628
|
+
out[] = UNDEF
|
|
629
|
+
m = grouped.elements
|
|
630
|
+
return out if m.zero?
|
|
631
|
+
# For each cell, the present cell its value comes from: the nearest one at
|
|
632
|
+
# or before it (forward) or at or after it (backward), kept when it lies
|
|
633
|
+
# in the cell's own segment.
|
|
634
|
+
slot = CArray.int64(m).seq!
|
|
635
|
+
slot[grouped.is_masked] = backward ? m : -1 if grouped.has_mask?
|
|
636
|
+
seg = CArray.segment_index(offsets: @bounds)
|
|
637
|
+
if backward
|
|
638
|
+
source = slot.reverse.cummin.reverse
|
|
639
|
+
found = source.lt(@bounds[seg + 1])
|
|
640
|
+
else
|
|
641
|
+
source = slot.cummax
|
|
642
|
+
found = source.ge(@bounds[seg])
|
|
643
|
+
end
|
|
644
|
+
filled = grouped[source.pmin(m - 1).pmax(0)].copy
|
|
645
|
+
filled[found.not] = UNDEF
|
|
646
|
+
out.reshape(@value.elements)[perm] = filled
|
|
647
|
+
out
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# @overload order(descending: false, method: :ordinal, kind: :quick)
|
|
651
|
+
# Returns each cell's rank among the members of its segment, source-shaped
|
|
652
|
+
# int64, as `CArray#order` ranks a 1-D array: `0` is the smallest, ties
|
|
653
|
+
# are broken by position (`:ordinal`) or share a rank (`:dense`), and
|
|
654
|
+
# `descending:` reverses the order. A masked cell, and a cell in no
|
|
655
|
+
# segment, is UNDEF.
|
|
656
|
+
# @return [CArray]
|
|
657
|
+
def order (axis: nil, descending: false, method: :ordinal, kind: :quick)
|
|
658
|
+
return axis_order(axis, descending: descending, method: method, kind: kind) if axis
|
|
659
|
+
out = CArray.int64(*@src_shape)
|
|
660
|
+
out[] = UNDEF
|
|
661
|
+
# The keywords are checked by the core, on no cells, so a segment
|
|
662
|
+
# iterator refuses what CArray#order refuses.
|
|
663
|
+
CArray.int64(0).order(descending: descending, method: method, kind: kind)
|
|
664
|
+
# The core ranks every value once, so the order and the ties are its own.
|
|
665
|
+
# Ranked in the asked direction, so tied cells keep their order either way.
|
|
666
|
+
ranks = grouped.order(method: :dense, kind: kind, descending: descending)
|
|
667
|
+
m = grouped.elements
|
|
668
|
+
return out if m.zero?
|
|
669
|
+
seg = CArray.segment_index(offsets: @bounds)
|
|
670
|
+
present = ranks.is_not_masked
|
|
671
|
+
# One key per cell, segment first and a masked cell last in its segment;
|
|
672
|
+
# a stable sort keeps tied cells in their order.
|
|
673
|
+
key = seg * (m + 1) + ranks.strip_mask(m)
|
|
674
|
+
sorted = key.sort_index(kind: :stable)
|
|
675
|
+
first = @bounds[seg[sorted]] # where each sorted cell's segment starts
|
|
676
|
+
local = CArray.int64(m)
|
|
677
|
+
if method == :dense
|
|
678
|
+
skey = key[sorted]
|
|
679
|
+
step = CArray.int64(m)
|
|
680
|
+
step[] = 1
|
|
681
|
+
step[1..-1] = skey[1..-1].ne(skey[0...-1]).int64 if m > 1
|
|
682
|
+
run = step.cumsum.int64
|
|
683
|
+
local[sorted] = run - run[first]
|
|
684
|
+
else
|
|
685
|
+
local[sorted] = CArray.int64(m).seq! - first
|
|
686
|
+
end
|
|
687
|
+
local[present.not] = UNDEF
|
|
688
|
+
out.reshape(@value.elements)[perm] = local
|
|
689
|
+
out
|
|
532
690
|
end
|
|
533
691
|
|
|
534
692
|
private
|
|
@@ -557,7 +715,7 @@ class CASegmentIterator < CAIterator
|
|
|
557
715
|
# one that no cell belongs to: every cell comes back UNDEF, as it does when
|
|
558
716
|
# there are segments and a cell is in none of them.
|
|
559
717
|
def scan (op)
|
|
560
|
-
CArray.
|
|
718
|
+
CArray.__name_errors_after_method__(op) { scan_answered(op) }
|
|
561
719
|
end
|
|
562
720
|
|
|
563
721
|
def scan_answered (op)
|
|
@@ -570,14 +728,14 @@ class CASegmentIterator < CAIterator
|
|
|
570
728
|
bundle = @k > 0 ? [codes, @k, [0]]
|
|
571
729
|
: [CArray.int64(@value.elements).fill(-1), 1, [0]]
|
|
572
730
|
scan_source.reshape(@value.elements)
|
|
573
|
-
.
|
|
731
|
+
.__scan_by_axis_groups__([0], [bundle], op)
|
|
574
732
|
.reshape(*@src_shape)
|
|
575
733
|
end
|
|
576
734
|
|
|
577
735
|
# The core's scan of one cell of the value: the scan answers in its data
|
|
578
736
|
# type and Face. A scan the core refuses raises here, in the core's words.
|
|
579
737
|
def core_scan_answer (op)
|
|
580
|
-
(@core_scan_answer ||= {})[op] ||= core_probe.public_send(op)
|
|
738
|
+
(@core_scan_answer ||= {})[op] ||= CAIterator.core_probe(@value).public_send(op)
|
|
581
739
|
end
|
|
582
740
|
|
|
583
741
|
# Whether the axis-group scan kernel gives the core's answer. It reads
|
|
@@ -587,6 +745,7 @@ class CASegmentIterator < CAIterator
|
|
|
587
745
|
# cumcount in int64 -- which is not the core's answer for every data type
|
|
588
746
|
# (the core counts a boolean's running sum in uint64).
|
|
589
747
|
def kernel_scans? (op, answer)
|
|
748
|
+
return false unless CArray.__fast_paths_enabled__
|
|
590
749
|
return false if @value.face? || answer.face? || @value.complex?
|
|
591
750
|
kernel_type =
|
|
592
751
|
case op
|
|
@@ -634,7 +793,7 @@ class CASegmentIterator < CAIterator
|
|
|
634
793
|
if model.face? && model.read_only?
|
|
635
794
|
CArray.new(CA_OBJECT, shape)
|
|
636
795
|
else
|
|
637
|
-
model.__send__(:
|
|
796
|
+
model.__send__(:blank_like, shape)
|
|
638
797
|
end
|
|
639
798
|
end
|
|
640
799
|
|
|
@@ -683,8 +842,9 @@ class CASegmentIterator < CAIterator
|
|
|
683
842
|
|
|
684
843
|
# Lay a per-cell weight array out in segment-contiguous order (same layout as
|
|
685
844
|
# @grouped), so wsum / wmean can pair each segment's values with its weights.
|
|
686
|
-
# Weights are coerced to float64
|
|
687
|
-
# the
|
|
845
|
+
# Weights are coerced to float64 and gathered through the same permutation
|
|
846
|
+
# as the values, which carries the weight mask and leaves out the excluded
|
|
847
|
+
# cells, so wg lines up with @grouped.
|
|
688
848
|
# The weighted kernel reads float64 weights; the per-segment fallback hands
|
|
689
849
|
# them to the core as they are, so an object weight stays exact.
|
|
690
850
|
def scatter_weights (weights, as_float64 = true)
|
|
@@ -694,10 +854,10 @@ class CASegmentIterator < CAIterator
|
|
|
694
854
|
"value.elements (#{codes.elements})"
|
|
695
855
|
end
|
|
696
856
|
wf = as_float64 ? weights.float64 : weights
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
857
|
+
wf = wf.reshape(wf.elements)
|
|
858
|
+
# An empty index into an empty source is out of range, so take the empty
|
|
859
|
+
# layout directly.
|
|
860
|
+
grouped.elements > 0 ? wf[perm].copy : wf[0...0].copy
|
|
701
861
|
end
|
|
702
862
|
|
|
703
863
|
# Map a per-segment index to the flat source address via the
|
|
@@ -735,6 +895,37 @@ class CASegmentIterator < CAIterator
|
|
|
735
895
|
out
|
|
736
896
|
end
|
|
737
897
|
|
|
898
|
+
# The value at the first or last present cell of each segment, UNDEF for a
|
|
899
|
+
# segment with none. Every segment's position is found at once: for each
|
|
900
|
+
# grouped slot, the nearest present slot at or after it (a running minimum
|
|
901
|
+
# taken from the end) or at or before it (a running maximum), read at the
|
|
902
|
+
# segment's first or last slot and kept when it falls inside the segment.
|
|
903
|
+
# A read-only Face cannot take UNDEF cell by cell, so its answers are
|
|
904
|
+
# collected segment by segment, as its other reductions are.
|
|
905
|
+
def edge_values (op)
|
|
906
|
+
n = grouped.elements
|
|
907
|
+
if n.zero? || (@value.face? && ! face_output?) || ! CArray.__fast_paths_enabled__
|
|
908
|
+
return per_segment(core_reduce_kind(op)) { |s| s.public_send(op) }
|
|
909
|
+
end
|
|
910
|
+
slot = CArray.int64(n).seq!
|
|
911
|
+
if grouped.has_mask?
|
|
912
|
+
slot[grouped.is_masked] = (op == :first) ? n : -1
|
|
913
|
+
end
|
|
914
|
+
lo, hi = @bounds[0...-1], @bounds[1..-1]
|
|
915
|
+
if op == :first
|
|
916
|
+
nearest = slot.reverse.cummin.reverse
|
|
917
|
+
at = nearest[lo.pmin(n - 1)]
|
|
918
|
+
else
|
|
919
|
+
nearest = slot.cummax
|
|
920
|
+
at = nearest[(hi - 1).pmax(0)]
|
|
921
|
+
end
|
|
922
|
+
found = at.ge(lo) & at.lt(hi)
|
|
923
|
+
out = grouped[at.pmin(n - 1).pmax(0)].copy
|
|
924
|
+
out[found.not] = UNDEF
|
|
925
|
+
# A reduction carries no attributes, a picked value included.
|
|
926
|
+
grouped.__send__(:drop_carried_attrs, out)
|
|
927
|
+
end
|
|
928
|
+
|
|
738
929
|
# The members of segment `c` as a CArray slice of the grouped copy. An empty
|
|
739
930
|
# segment (zero-width segment) yields the shared empty array — a zero-length
|
|
740
931
|
# slice cannot be taken directly, and an empty array carries the same reduction
|
|
@@ -754,10 +945,19 @@ class CASegmentIterator < CAIterator
|
|
|
754
945
|
MONOID_TYPES = %i[int8 uint8 int16 uint16 int32 uint32
|
|
755
946
|
int64 uint64 float32 float64].freeze
|
|
756
947
|
|
|
948
|
+
# Whether the C reduceat and scatter kernels read a value of +data_type+:
|
|
949
|
+
# a numeric one, with the fast paths on. They are a faster way to the
|
|
950
|
+
# core's answer for each group; a development build run with
|
|
951
|
+
# CARRAY_GENERAL_PATH set reduces every group by the core instead, which
|
|
952
|
+
# is how the two are compared.
|
|
953
|
+
def kernel_reads? (data_type = grouped.data_type)
|
|
954
|
+
MONOID_TYPES.include?(data_type) && CArray.__fast_paths_enabled__
|
|
955
|
+
end
|
|
956
|
+
|
|
757
957
|
def moments
|
|
758
958
|
return @moments if defined?(@moments)
|
|
759
959
|
@moments =
|
|
760
|
-
if
|
|
960
|
+
if kernel_reads?
|
|
761
961
|
dt = grouped.data_type
|
|
762
962
|
counts = CArray.int64(@k)
|
|
763
963
|
sums = CArray.float64(@k)
|
|
@@ -773,7 +973,7 @@ class CASegmentIterator < CAIterator
|
|
|
773
973
|
def arg_minmax
|
|
774
974
|
return @arg_minmax if defined?(@arg_minmax)
|
|
775
975
|
@arg_minmax =
|
|
776
|
-
if
|
|
976
|
+
if kernel_reads?
|
|
777
977
|
mn = CArray.int64(@k)
|
|
778
978
|
mx = CArray.int64(@k)
|
|
779
979
|
grouped.send(:__reduceat_argminmax__, @offsets, mn, mx)
|
|
@@ -787,7 +987,7 @@ class CASegmentIterator < CAIterator
|
|
|
787
987
|
def all_any
|
|
788
988
|
return @all_any if defined?(@all_any)
|
|
789
989
|
@all_any =
|
|
790
|
-
if grouped.data_type == CA_BOOLEAN
|
|
990
|
+
if grouped.data_type == CA_BOOLEAN && CArray.__fast_paths_enabled__
|
|
791
991
|
a = CArray.boolean(@k)
|
|
792
992
|
o = CArray.boolean(@k)
|
|
793
993
|
grouped.send(:__reduceat_all_any__, @offsets, a, o)
|
|
@@ -807,20 +1007,18 @@ class CASegmentIterator < CAIterator
|
|
|
807
1007
|
# per-fiber form -- and `accumulate(axis:)`, the one axis: member that needs
|
|
808
1008
|
# this, is exactly the case that would have found it missing.
|
|
809
1009
|
def core_reduce_type (op, *args)
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
CA_OBJECT : core_probe.public_send(op, *args, axis: 1).data_type
|
|
1010
|
+
kind = core_reduce_kind(op, *args)
|
|
1011
|
+
kind ? kind.data_type : CA_OBJECT
|
|
813
1012
|
end
|
|
814
1013
|
|
|
815
|
-
#
|
|
816
|
-
#
|
|
817
|
-
#
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
@value.reshape(@value.elements)[[0]].reshape(1, 1)
|
|
1014
|
+
# The core's answer itself for the one-cell probe, or nil for a Face with no
|
|
1015
|
+
# cell to probe. Its class is the one the per-segment answers come in, which
|
|
1016
|
+
# for a Face need not be the values' own (a CATime's spread is a CATimedelta).
|
|
1017
|
+
def core_reduce_kind (op, *args)
|
|
1018
|
+
memo = (@core_reduce_kind ||= {})
|
|
1019
|
+
return memo[[op, args]] if memo.key?([op, args])
|
|
1020
|
+
probe = CAIterator.core_probe(@value)
|
|
1021
|
+
memo[[op, args]] = probe && probe.public_send(op, *args, axis: 1)
|
|
824
1022
|
end
|
|
825
1023
|
|
|
826
1024
|
# Whether an output can be built by lifting one. A Face is filled by writing
|
|
@@ -838,15 +1036,22 @@ class CASegmentIterator < CAIterator
|
|
|
838
1036
|
# segment's members — the mask carries the "insufficient present data" contract
|
|
839
1037
|
# for free (an all-masked segment reduces like an empty one; identity-bearing
|
|
840
1038
|
# reductions return their identity, ratios return UNDEF).
|
|
841
|
-
def per_segment (
|
|
842
|
-
|
|
843
|
-
|
|
1039
|
+
def per_segment (kind)
|
|
1040
|
+
# a data type the values' own Face answers in names that Face
|
|
1041
|
+
kind = @value if ! kind.is_a?(CArray) && @value.face? && kind == @value.data_type
|
|
1042
|
+
data_type = kind.is_a?(CArray) ? kind.data_type : kind
|
|
1043
|
+
out = if kind.nil?
|
|
1044
|
+
CArray.new(CA_OBJECT, [@k])
|
|
1045
|
+
elsif ! (kind.is_a?(CArray) && kind.face?)
|
|
1046
|
+
bytes = if kind.is_a?(CArray) then kind.bytes
|
|
1047
|
+
elsif data_type == @value.data_type then @value.bytes
|
|
1048
|
+
else 0 end
|
|
844
1049
|
CArray.new(data_type, [@k], bytes: bytes)
|
|
845
|
-
elsif
|
|
846
|
-
# the core answered in
|
|
847
|
-
#
|
|
848
|
-
#
|
|
849
|
-
|
|
1050
|
+
elsif ! kind.read_only?
|
|
1051
|
+
# the core answered in a Face, so the output is one too:
|
|
1052
|
+
# CATime#min hands back a CATime::Element, which only a CATime
|
|
1053
|
+
# has anywhere to put
|
|
1054
|
+
kind.__send__(:blank_like, [@k])
|
|
850
1055
|
else
|
|
851
1056
|
# a read-only Face cannot be filled, so its answers are collected
|
|
852
1057
|
# as the surface objects they already are
|
|
@@ -860,6 +1065,10 @@ class CASegmentIterator < CAIterator
|
|
|
860
1065
|
|
|
861
1066
|
# ---- no axis: form -------------------------------------------------------
|
|
862
1067
|
|
|
1068
|
+
def axis_by_core (*)
|
|
1069
|
+
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
1070
|
+
end
|
|
1071
|
+
|
|
863
1072
|
def axis_by_masked_copy (*)
|
|
864
1073
|
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
865
1074
|
end
|
|
@@ -884,6 +1093,18 @@ class CASegmentIterator < CAIterator
|
|
|
884
1093
|
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
885
1094
|
end
|
|
886
1095
|
|
|
1096
|
+
def axis_lag (*)
|
|
1097
|
+
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
def axis_order (*)
|
|
1101
|
+
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
def axis_hold (*)
|
|
1105
|
+
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
1106
|
+
end
|
|
1107
|
+
|
|
887
1108
|
def axis_map (*)
|
|
888
1109
|
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
889
1110
|
end
|
|
@@ -931,37 +1152,47 @@ class CASegmentIterator < CAIterator
|
|
|
931
1152
|
(op == :count && (args.empty? || args[0].equal?(UNDEF)))
|
|
932
1153
|
end
|
|
933
1154
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
count_not_masked(**kw)
|
|
1155
|
+
CArray.send(:define_named_methods, self, REDUCTIONS, :reduce_counted, super_block: true)
|
|
1156
|
+
|
|
1157
|
+
# The segment reduction already leaves an empty segment as the core does,
|
|
1158
|
+
# so only a min_count asking for more is applied here. The reduction the
|
|
1159
|
+
# member is made of is the block (super).
|
|
1160
|
+
private def reduce_counted (op, args, kw)
|
|
1161
|
+
min_count = kw.delete(:min_count)
|
|
1162
|
+
fill_value = kw.delete(:fill_value)
|
|
1163
|
+
need = CArray.__present_cells_needed_per_result_cell__(op, min_count, 0)
|
|
1164
|
+
result = yield(*args, **kw)
|
|
1165
|
+
return result if need == 0 && fill_value.nil?
|
|
1166
|
+
if need > 0
|
|
1167
|
+
present = if op == :wsum || op == :wmean
|
|
1168
|
+
present_pair_count(args[0], kw[:axis])
|
|
1169
|
+
elsif ReductionCountAndFill.mask_count?(op, args)
|
|
1170
|
+
if kw[:axis]
|
|
1171
|
+
raise NotImplementedError,
|
|
1172
|
+
"#{self.class}##{op}(axis:, min_count:) is not implemented"
|
|
953
1173
|
end
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1174
|
+
elements
|
|
1175
|
+
else
|
|
1176
|
+
count_not_masked(**kw)
|
|
1177
|
+
end
|
|
1178
|
+
short = present.lt(need)
|
|
1179
|
+
end
|
|
1180
|
+
if short
|
|
1181
|
+
(result.is_a?(Array) ? result : [result]).each { |r| r[short] = UNDEF }
|
|
960
1182
|
end
|
|
1183
|
+
CArray.__fill_undefined_result_cells__(result, fill_value)
|
|
961
1184
|
end
|
|
962
1185
|
end
|
|
963
1186
|
prepend ReductionCountAndFill
|
|
964
1187
|
|
|
1188
|
+
|
|
1189
|
+
# lag and strip_mask(method:) give the source's values moved or held in
|
|
1190
|
+
# place, so the result shows the source's attributes, as CArray#lag and
|
|
1191
|
+
# CArray#strip_mask(method:) do.
|
|
1192
|
+
prepend(Module.new do
|
|
1193
|
+
def lag (*args, **keywords) = @value.__send__(:carry_attrs, super)
|
|
1194
|
+
def strip_mask (**keywords) = @value.__send__(:carry_attrs, super)
|
|
1195
|
+
end)
|
|
965
1196
|
end
|
|
966
1197
|
|
|
967
1198
|
|