carray 3.0.3 → 3.0.5
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 +469 -0
- data/README.md +3 -2
- data/carray.gemspec +1 -1
- data/ext/ca_array_pool.c +25 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +14 -26
- 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 +231 -153
- data/ext/ca_compare.c +9 -20
- data/ext/ca_composite_dispatch.c +9 -9
- data/ext/ca_group_iter.c +16 -17
- data/ext/ca_kernel_iterator.c +1240 -2000
- data/ext/ca_kernel_iterator.h +502 -519
- data/ext/ca_monop_dispatch.c +82 -59
- data/ext/ca_monop_dispatch.h +1 -0
- data/ext/ca_obj_array.c +152 -45
- data/ext/ca_obj_bincmp.c +25 -21
- data/ext/ca_obj_binop.c +16 -26
- data/ext/ca_obj_bitarray.c +8 -3
- data/ext/ca_obj_bitfield.c +63 -49
- data/ext/ca_obj_block.c +18 -9
- data/ext/ca_obj_byte_swap.c +7 -7
- data/ext/ca_obj_const_string.c +188 -22
- data/ext/ca_obj_face.c +280 -58
- 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 +30 -8
- data/ext/ca_obj_fixlen_string.c +2 -3
- data/ext/ca_obj_grid.c +455 -134
- data/ext/ca_obj_meld.c +172 -34
- data/ext/ca_obj_moncmp.c +23 -8
- data/ext/ca_obj_monop.c +113 -67
- data/ext/ca_obj_object.c +210 -52
- data/ext/ca_obj_record.c +15 -7
- data/ext/ca_obj_reduce.c +17 -8
- data/ext/ca_obj_refer.c +42 -35
- data/ext/ca_obj_remap.c +30 -9
- data/ext/ca_obj_repeat.c +9 -10
- data/ext/ca_obj_roll.c +31 -10
- data/ext/ca_obj_select.c +48 -14
- data/ext/ca_obj_select_axis.c +15 -47
- data/ext/ca_obj_shift.c +41 -7
- data/ext/ca_obj_stack.c +180 -34
- data/ext/ca_obj_stride.c +190 -134
- data/ext/ca_obj_string.c +80 -4
- data/ext/ca_obj_tile.c +32 -11
- data/ext/ca_obj_time.c +22 -6
- data/ext/ca_obj_timedelta.c +22 -6
- data/ext/ca_obj_transpose.c +13 -7
- data/ext/ca_obj_triop.c +26 -32
- data/ext/ca_obj_window.c +267 -74
- data/ext/ca_op_ipower.c +4 -4
- data/ext/ca_sort_kernels.h +6 -3
- data/ext/ca_sweep_engine.c +2 -2
- data/ext/caframe_csv_reader.c +80 -1
- data/ext/carray.h +214 -75
- data/ext/carray_access.c +200 -99
- data/ext/carray_address_basis.c +343 -87
- data/ext/carray_attribute.c +113 -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 +375 -96
- data/ext/carray_cast_func.rb +90 -20
- data/ext/carray_class.c +4 -3
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +18 -9
- data/ext/carray_copy.c +51 -14
- data/ext/carray_core.c +150 -55
- data/ext/carray_count.c +56 -20
- data/ext/carray_element.c +81 -33
- data/ext/carray_factorize.c +138 -84
- data/ext/carray_generate.c +199 -52
- data/ext/carray_histogram.c +63 -18
- data/ext/carray_hold.c +12 -15
- data/ext/carray_index_classifier.c +388 -176
- data/ext/carray_internal.h +348 -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 +10897 -0
- data/ext/carray_kernels_triop.c +1864 -0
- data/ext/carray_key_windows.c +220 -0
- data/ext/carray_lazy.c +170 -111
- data/ext/carray_mask.c +337 -94
- data/ext/carray_median_percentile.c +82 -28
- data/ext/carray_memory_view.c +669 -561
- data/ext/carray_operator.c +129 -52
- data/ext/carray_order.c +604 -77
- data/ext/carray_partition.c +28 -27
- data/ext/carray_random.c +85 -31
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +87 -45
- data/ext/carray_segment.c +3 -3
- data/ext/carray_slab.c +123 -37
- data/ext/carray_sort.c +259 -51
- data/ext/carray_sort_kernel.c +5 -7
- data/ext/carray_struct.c +54 -61
- data/ext/carray_test.c +26 -5
- data/ext/carray_time_text.c +4 -2
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +309 -66
- data/ext/mk_call_cfunc.rb +19 -2
- data/ext/mkkernel.rb +403 -350
- data/ext/ruby_carray.c +10 -4
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +139 -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 +105 -17
- 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 +195 -59
- data/lib/carray/categorical_iterator.rb +132 -29
- data/lib/carray/complex.rb +5 -5
- data/lib/carray/conditional.rb +67 -18
- data/lib/carray/const_string.rb +24 -5
- data/lib/carray/construct.rb +15 -11
- data/lib/carray/data_type_extension.rb +9 -1
- data/lib/carray/distance_limit.rb +77 -0
- data/lib/carray/fixlen_string.rb +48 -3
- data/lib/carray/frame/column_verbs.rb +71 -0
- data/lib/carray/frame/concat.rb +17 -3
- 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 +252 -28
- data/lib/carray/frame/group.rb +603 -101
- data/lib/carray/frame/io.rb +117 -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 +42 -6
- 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 +217 -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 +61 -37
- 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 +323 -88
- data/lib/carray/serialize.rb +45 -14
- data/lib/carray/slab_iterator.rb +50 -0
- data/lib/carray/stack.rb +15 -12
- data/lib/carray/string.rb +113 -5
- data/lib/carray/string_operation_extension.rb +21 -6
- data/lib/carray/struct.rb +9 -21
- data/lib/carray/struct_builder.rb +24 -11
- data/lib/carray/time.rb +819 -158
- 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_transpose.rb +5 -4
- data/yard-stubs/ca_obj_window.rb +8 -3
- data/yard-stubs/carray_access.rb +6 -1
- data/yard-stubs/carray_attribute.rb +3 -6
- data/yard-stubs/carray_cast.rb +12 -0
- data/yard-stubs/carray_conversion.rb +7 -2
- data/yard-stubs/carray_copy.rb +4 -2
- data/yard-stubs/carray_count.rb +4 -4
- data/yard-stubs/carray_element.rb +17 -10
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_mask.rb +32 -3
- data/yard-stubs/carray_math.rb +41 -5
- data/yard-stubs/carray_median_percentile.rb +5 -2
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +129 -22
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +17 -10
- 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,13 +251,21 @@ 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
|
+
centred_variance(m, 1)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The kernel divides the centred sum of squares by count - ddof once, as
|
|
259
|
+
# the core reduction does; scaling the sample variance by (n-1)/n instead
|
|
260
|
+
# rounds twice and lands an ulp away.
|
|
261
|
+
def centred_variance (m, ddof)
|
|
255
262
|
cnt = m[:count]
|
|
256
|
-
means = m[:sum] / cnt.float64 # per-segment mean (garbage where count
|
|
257
|
-
out = CArray.float64(@k) #
|
|
258
|
-
grouped.send(:__reduceat_variance__, @offsets, means, cnt, out)
|
|
263
|
+
means = m[:sum] / cnt.float64 # per-segment mean (garbage where the count
|
|
264
|
+
out = CArray.float64(@k) # is too small; the kernel masks those)
|
|
265
|
+
grouped.send(:__reduceat_variance__, @offsets, means, cnt, out, ddof)
|
|
259
266
|
out
|
|
260
267
|
end
|
|
268
|
+
private :centred_variance
|
|
261
269
|
|
|
262
270
|
# @overload stddev
|
|
263
271
|
# Returns per-segment SAMPLE standard deviation (ddof=1) as float64.
|
|
@@ -267,7 +275,7 @@ class CASegmentIterator < CAIterator
|
|
|
267
275
|
def stddev(axis: nil)
|
|
268
276
|
return axis_by_masked_copy(axis, :stddev) if axis
|
|
269
277
|
m = moments
|
|
270
|
-
return per_segment(
|
|
278
|
+
return per_segment(core_reduce_kind(:stddev)) { |s| s.stddev } unless m
|
|
271
279
|
variance.sqrt # sqrt propagates the n<2 mask
|
|
272
280
|
end
|
|
273
281
|
|
|
@@ -278,7 +286,7 @@ class CASegmentIterator < CAIterator
|
|
|
278
286
|
# @return [CArray]
|
|
279
287
|
def prod(axis: nil)
|
|
280
288
|
return axis_prod(axis) if axis
|
|
281
|
-
return per_segment(
|
|
289
|
+
return per_segment(core_reduce_kind(:prod)) { |s| s.prod } unless kernel_reads?
|
|
282
290
|
out = CArray.float64(@k)
|
|
283
291
|
grouped.send(:__reduceat_prod__, @offsets, out)
|
|
284
292
|
out
|
|
@@ -304,6 +312,48 @@ class CASegmentIterator < CAIterator
|
|
|
304
312
|
aa ? aa[:any] : per_segment(CA_BOOLEAN) { |s| s.any }
|
|
305
313
|
end
|
|
306
314
|
|
|
315
|
+
# @overload first
|
|
316
|
+
# Returns the first present value of each segment, in the value's data
|
|
317
|
+
# type (matching `CArray#first`). A segment with no present value is
|
|
318
|
+
# UNDEF.
|
|
319
|
+
# @return [CArray]
|
|
320
|
+
def first (axis: nil)
|
|
321
|
+
return axis_by_core(axis, :first) if axis
|
|
322
|
+
edge_values(:first)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# @overload last
|
|
326
|
+
# Returns the last present value of each segment, in the value's data
|
|
327
|
+
# type (matching `CArray#last`). A segment with no present value is
|
|
328
|
+
# UNDEF.
|
|
329
|
+
# @return [CArray]
|
|
330
|
+
def last (axis: nil)
|
|
331
|
+
return axis_by_core(axis, :last) if axis
|
|
332
|
+
edge_values(:last)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# @overload nunique
|
|
336
|
+
# Returns the number of distinct present values in each segment as int64,
|
|
337
|
+
# with the distinctness of `CArray#nunique` (every NaN is one value,
|
|
338
|
+
# -0.0 is 0.0). A segment with no present value counts 0.
|
|
339
|
+
# @return [CArray]
|
|
340
|
+
def nunique (axis: nil)
|
|
341
|
+
return axis_by_core(axis, :nunique) if axis
|
|
342
|
+
return per_segment(CA_INT64) { |s| s.nunique } unless CArray.__fast_paths_enabled__
|
|
343
|
+
out = CArray.int64(@k)
|
|
344
|
+
return out if grouped.elements.zero?
|
|
345
|
+
# Each cell's value becomes a code, so a distinct (segment, value) pair is
|
|
346
|
+
# a distinct int64 key; the keys' segments, counted, are the answer.
|
|
347
|
+
values = grouped.categorize
|
|
348
|
+
width = [values.labels.size, 1].max
|
|
349
|
+
keys = CArray.segment_index(offsets: @bounds) * width + values.codes.int64
|
|
350
|
+
owners = keys.unique / width
|
|
351
|
+
return out if owners.elements.zero?
|
|
352
|
+
counts = owners.bincount
|
|
353
|
+
out[0...counts.elements] = counts
|
|
354
|
+
out
|
|
355
|
+
end
|
|
356
|
+
|
|
307
357
|
# ---- tier 2 (fused / population / position) ------------------------------
|
|
308
358
|
|
|
309
359
|
# @overload minmax
|
|
@@ -324,18 +374,16 @@ class CASegmentIterator < CAIterator
|
|
|
324
374
|
|
|
325
375
|
# @overload variancep
|
|
326
376
|
# Per-segment POPULATION variance (ddof=0) as float64, matching
|
|
327
|
-
# `CArray#variancep`: empty / all-masked -> MASKED, single value -> 0.0
|
|
377
|
+
# `CArray#variancep`: empty / all-masked -> MASKED, single value -> 0.0
|
|
378
|
+
# (NaN when that value is a NaN or an infinity).
|
|
328
379
|
# Derived from the sample variance (variancep = variance * (n-1) / n), so it
|
|
329
380
|
# reuses the centred two-pass kernel with no extra walk.
|
|
330
381
|
# @return [CArray]
|
|
331
382
|
def variancep(axis: nil)
|
|
332
383
|
return axis_by_masked_copy(axis, :variancep) if axis
|
|
333
384
|
m = moments
|
|
334
|
-
return per_segment(
|
|
335
|
-
|
|
336
|
-
vp = variance * (cnt - 1).float64 / cnt.float64
|
|
337
|
-
vp[cnt.eq(1)] = 0.0 # one value: defined, unlike variance
|
|
338
|
-
vp
|
|
385
|
+
return per_segment(core_reduce_kind(:variancep)) { |s| s.variancep } unless m
|
|
386
|
+
centred_variance(m, 0) # one value: 0.0, or NaN for a NaN or an infinity
|
|
339
387
|
end
|
|
340
388
|
|
|
341
389
|
# @overload stddevp
|
|
@@ -344,7 +392,7 @@ class CASegmentIterator < CAIterator
|
|
|
344
392
|
def stddevp(axis: nil)
|
|
345
393
|
return axis_by_masked_copy(axis, :stddevp) if axis
|
|
346
394
|
m = moments
|
|
347
|
-
return per_segment(
|
|
395
|
+
return per_segment(core_reduce_kind(:stddevp)) { |s| s.stddevp } unless m
|
|
348
396
|
variancep.sqrt
|
|
349
397
|
end
|
|
350
398
|
|
|
@@ -421,7 +469,7 @@ class CASegmentIterator < CAIterator
|
|
|
421
469
|
# @return [CArray]
|
|
422
470
|
def wsum (weights, axis: nil)
|
|
423
471
|
return axis_wsum_wmean(weights, axis, :wsum)[0] if axis
|
|
424
|
-
kernel =
|
|
472
|
+
kernel = kernel_reads?
|
|
425
473
|
wg = scatter_weights(weights, kernel)
|
|
426
474
|
return kernel_weighted(wg)[0] if kernel
|
|
427
475
|
fold_weighted(:wsum, wg, 0.0) { |v, ws| v.wsum(ws) }
|
|
@@ -435,7 +483,7 @@ class CASegmentIterator < CAIterator
|
|
|
435
483
|
# @return [CArray]
|
|
436
484
|
def wmean (weights, axis: nil)
|
|
437
485
|
return axis_wsum_wmean(weights, axis, :wmean)[1] if axis
|
|
438
|
-
kernel =
|
|
486
|
+
kernel = kernel_reads?
|
|
439
487
|
wg = scatter_weights(weights, kernel)
|
|
440
488
|
return kernel_weighted(wg)[1] if kernel
|
|
441
489
|
fold_weighted(:wmean, wg, UNDEF) { |v, ws| v.wmean(ws) }
|
|
@@ -507,7 +555,7 @@ class CASegmentIterator < CAIterator
|
|
|
507
555
|
# data type is the core's (`cumsum` of a boolean is uint64, of an integer
|
|
508
556
|
# float64), a Face keeps its Face (`cummin` of a time column is a time), and
|
|
509
557
|
# a scan the core refuses (`cumsum` of a time column) raises in the core's
|
|
510
|
-
# words. When the fused C kernel
|
|
558
|
+
# words. When the fused C kernel __scan_by_axis_groups__ answers in that data
|
|
511
559
|
# type, it runs with the whole source as a single grouped axis and the
|
|
512
560
|
# segment of each cell (#codes) as the single bundle, emitting in source
|
|
513
561
|
# order; otherwise each segment is scanned by the core and scattered back.
|
|
@@ -527,8 +575,122 @@ class CASegmentIterator < CAIterator
|
|
|
527
575
|
# @!method cumcount
|
|
528
576
|
# Per-segment 1-based within-segment ordinal (int64), source-shaped.
|
|
529
577
|
# @return [CArray]
|
|
530
|
-
|
|
531
|
-
|
|
578
|
+
CArray.send(:define_named_methods, self,
|
|
579
|
+
[:cumsum, :cumprod, :cummax, :cummin, :cumcount], :segment_scan)
|
|
580
|
+
|
|
581
|
+
private def segment_scan (op, axis: nil)
|
|
582
|
+
axis ? axis_scan(op, axis) : scan(op)
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
# @overload lag(n = 1)
|
|
586
|
+
# Returns the value `n` steps earlier within each segment, source-shaped,
|
|
587
|
+
# as `CArray#lag` does along an axis. A cell whose segment has no value
|
|
588
|
+
# `n` steps back is UNDEF, so a lag never reaches into the segment before;
|
|
589
|
+
# a cell in no segment is UNDEF. A negative `n` looks ahead.
|
|
590
|
+
# @param n [Integer]
|
|
591
|
+
# @return [CArray]
|
|
592
|
+
def lag (n = 1, axis: nil)
|
|
593
|
+
unless n.is_a?(Integer)
|
|
594
|
+
raise TypeError, "lag: n must be an Integer (got #{n.class})"
|
|
595
|
+
end
|
|
596
|
+
return axis_lag(n, axis) if axis
|
|
597
|
+
out = blank_like(@value, @src_shape)
|
|
598
|
+
out[] = UNDEF
|
|
599
|
+
m = grouped.elements
|
|
600
|
+
return out if m.zero?
|
|
601
|
+
# The copy is in segment order, so one shift moves every segment; the
|
|
602
|
+
# cells it brought across a segment boundary are then cut.
|
|
603
|
+
lagged = grouped.lag(n)
|
|
604
|
+
lagged = out.data_type == CA_OBJECT && lagged.face? ?
|
|
605
|
+
CArray.object(m) { lagged.to_a } : lagged.copy
|
|
606
|
+
unless n.zero?
|
|
607
|
+
seg = CArray.segment_index(offsets: @bounds)
|
|
608
|
+
start = @bounds[seg]
|
|
609
|
+
pos = CArray.int64(m).seq! - start
|
|
610
|
+
lagged[n > 0 ? pos.lt(n) : pos.ge(@bounds[seg + 1] - start + n)] = UNDEF
|
|
611
|
+
end
|
|
612
|
+
out.reshape(@value.elements)[perm] = lagged
|
|
613
|
+
out
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
# @overload strip_mask(method:)
|
|
617
|
+
# Returns the values with each masked cell filled from its segment, as
|
|
618
|
+
# `CArray#strip_mask(method:)` fills a 1-D array: `:forward` (`:ffill`)
|
|
619
|
+
# carries the segment's last present value forward, `:backward`
|
|
620
|
+
# (`:bfill`) its next one back. A fill never reaches across into another
|
|
621
|
+
# segment, so a masked cell with nothing to carry in its segment stays
|
|
622
|
+
# masked; a cell in no segment is UNDEF. The value is not modified.
|
|
623
|
+
# @param method [Symbol] `:forward`, `:ffill`, `:backward` or `:bfill`.
|
|
624
|
+
# @return [CArray] source-shaped
|
|
625
|
+
def strip_mask (method:, axis: nil)
|
|
626
|
+
backward = CAIterator.hold_backward?(method)
|
|
627
|
+
return axis_hold(method, axis) if axis
|
|
628
|
+
if @value.face? && ! face_output?
|
|
629
|
+
return scatter_segments(->(shape) { blank_like(@value, shape) }) { |s| s.strip_mask(method: method) }
|
|
630
|
+
end
|
|
631
|
+
out = blank_like(@value, @src_shape)
|
|
632
|
+
out[] = UNDEF
|
|
633
|
+
m = grouped.elements
|
|
634
|
+
return out if m.zero?
|
|
635
|
+
# For each cell, the present cell its value comes from: the nearest one at
|
|
636
|
+
# or before it (forward) or at or after it (backward), kept when it lies
|
|
637
|
+
# in the cell's own segment.
|
|
638
|
+
slot = CArray.int64(m).seq!
|
|
639
|
+
slot[grouped.is_masked] = backward ? m : -1 if grouped.has_mask?
|
|
640
|
+
seg = CArray.segment_index(offsets: @bounds)
|
|
641
|
+
if backward
|
|
642
|
+
source = slot.reverse.cummin.reverse
|
|
643
|
+
found = source.lt(@bounds[seg + 1])
|
|
644
|
+
else
|
|
645
|
+
source = slot.cummax
|
|
646
|
+
found = source.ge(@bounds[seg])
|
|
647
|
+
end
|
|
648
|
+
filled = grouped[source.pmin(m - 1).pmax(0)].copy
|
|
649
|
+
filled[found.not] = UNDEF
|
|
650
|
+
out.reshape(@value.elements)[perm] = filled
|
|
651
|
+
out
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
# @overload order(descending: false, method: :ordinal, kind: :quick)
|
|
655
|
+
# Returns each cell's rank among the members of its segment, source-shaped
|
|
656
|
+
# int64, as `CArray#order` ranks a 1-D array: `0` is the smallest, ties
|
|
657
|
+
# are broken by position (`:ordinal`) or share a rank (`:dense`), and
|
|
658
|
+
# `descending:` reverses the order. A masked cell, and a cell in no
|
|
659
|
+
# segment, is UNDEF.
|
|
660
|
+
# @return [CArray]
|
|
661
|
+
def order (axis: nil, descending: false, method: :ordinal, kind: :quick)
|
|
662
|
+
return axis_order(axis, descending: descending, method: method, kind: kind) if axis
|
|
663
|
+
out = CArray.int64(*@src_shape)
|
|
664
|
+
out[] = UNDEF
|
|
665
|
+
# The keywords are checked by the core, on no cells, so a segment
|
|
666
|
+
# iterator refuses what CArray#order refuses.
|
|
667
|
+
CArray.int64(0).order(descending: descending, method: method, kind: kind)
|
|
668
|
+
# The core ranks every value once, so the order and the ties are its own.
|
|
669
|
+
# Ranked in the asked direction, so tied cells keep their order either way.
|
|
670
|
+
ranks = grouped.order(method: :dense, kind: kind, descending: descending)
|
|
671
|
+
m = grouped.elements
|
|
672
|
+
return out if m.zero?
|
|
673
|
+
seg = CArray.segment_index(offsets: @bounds)
|
|
674
|
+
present = ranks.is_not_masked
|
|
675
|
+
# One key per cell, segment first and a masked cell last in its segment;
|
|
676
|
+
# a stable sort keeps tied cells in their order.
|
|
677
|
+
key = seg * (m + 1) + ranks.strip_mask(m)
|
|
678
|
+
sorted = key.sort_index(kind: :stable)
|
|
679
|
+
first = @bounds[seg[sorted]] # where each sorted cell's segment starts
|
|
680
|
+
local = CArray.int64(m)
|
|
681
|
+
if method == :dense
|
|
682
|
+
skey = key[sorted]
|
|
683
|
+
step = CArray.int64(m)
|
|
684
|
+
step[] = 1
|
|
685
|
+
step[1..-1] = skey[1..-1].ne(skey[0...-1]).int64 if m > 1
|
|
686
|
+
run = step.cumsum.int64
|
|
687
|
+
local[sorted] = run - run[first]
|
|
688
|
+
else
|
|
689
|
+
local[sorted] = CArray.int64(m).seq! - first
|
|
690
|
+
end
|
|
691
|
+
local[present.not] = UNDEF
|
|
692
|
+
out.reshape(@value.elements)[perm] = local
|
|
693
|
+
out
|
|
532
694
|
end
|
|
533
695
|
|
|
534
696
|
private
|
|
@@ -557,7 +719,7 @@ class CASegmentIterator < CAIterator
|
|
|
557
719
|
# one that no cell belongs to: every cell comes back UNDEF, as it does when
|
|
558
720
|
# there are segments and a cell is in none of them.
|
|
559
721
|
def scan (op)
|
|
560
|
-
CArray.
|
|
722
|
+
CArray.__name_errors_after_method__(op) { scan_answered(op) }
|
|
561
723
|
end
|
|
562
724
|
|
|
563
725
|
def scan_answered (op)
|
|
@@ -570,14 +732,14 @@ class CASegmentIterator < CAIterator
|
|
|
570
732
|
bundle = @k > 0 ? [codes, @k, [0]]
|
|
571
733
|
: [CArray.int64(@value.elements).fill(-1), 1, [0]]
|
|
572
734
|
scan_source.reshape(@value.elements)
|
|
573
|
-
.
|
|
735
|
+
.__scan_by_axis_groups__([0], [bundle], op)
|
|
574
736
|
.reshape(*@src_shape)
|
|
575
737
|
end
|
|
576
738
|
|
|
577
739
|
# The core's scan of one cell of the value: the scan answers in its data
|
|
578
740
|
# type and Face. A scan the core refuses raises here, in the core's words.
|
|
579
741
|
def core_scan_answer (op)
|
|
580
|
-
(@core_scan_answer ||= {})[op] ||= core_probe.public_send(op)
|
|
742
|
+
(@core_scan_answer ||= {})[op] ||= CAIterator.core_probe(@value).public_send(op)
|
|
581
743
|
end
|
|
582
744
|
|
|
583
745
|
# Whether the axis-group scan kernel gives the core's answer. It reads
|
|
@@ -587,6 +749,7 @@ class CASegmentIterator < CAIterator
|
|
|
587
749
|
# cumcount in int64 -- which is not the core's answer for every data type
|
|
588
750
|
# (the core counts a boolean's running sum in uint64).
|
|
589
751
|
def kernel_scans? (op, answer)
|
|
752
|
+
return false unless CArray.__fast_paths_enabled__
|
|
590
753
|
return false if @value.face? || answer.face? || @value.complex?
|
|
591
754
|
kernel_type =
|
|
592
755
|
case op
|
|
@@ -634,7 +797,7 @@ class CASegmentIterator < CAIterator
|
|
|
634
797
|
if model.face? && model.read_only?
|
|
635
798
|
CArray.new(CA_OBJECT, shape)
|
|
636
799
|
else
|
|
637
|
-
model.__send__(:
|
|
800
|
+
model.__send__(:blank_like, shape)
|
|
638
801
|
end
|
|
639
802
|
end
|
|
640
803
|
|
|
@@ -683,8 +846,9 @@ class CASegmentIterator < CAIterator
|
|
|
683
846
|
|
|
684
847
|
# Lay a per-cell weight array out in segment-contiguous order (same layout as
|
|
685
848
|
# @grouped), so wsum / wmean can pair each segment's values with its weights.
|
|
686
|
-
# Weights are coerced to float64
|
|
687
|
-
# the
|
|
849
|
+
# Weights are coerced to float64 and gathered through the same permutation
|
|
850
|
+
# as the values, which carries the weight mask and leaves out the excluded
|
|
851
|
+
# cells, so wg lines up with @grouped.
|
|
688
852
|
# The weighted kernel reads float64 weights; the per-segment fallback hands
|
|
689
853
|
# them to the core as they are, so an object weight stays exact.
|
|
690
854
|
def scatter_weights (weights, as_float64 = true)
|
|
@@ -694,10 +858,10 @@ class CASegmentIterator < CAIterator
|
|
|
694
858
|
"value.elements (#{codes.elements})"
|
|
695
859
|
end
|
|
696
860
|
wf = as_float64 ? weights.float64 : weights
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
861
|
+
wf = wf.reshape(wf.elements)
|
|
862
|
+
# An empty index into an empty source is out of range, so take the empty
|
|
863
|
+
# layout directly.
|
|
864
|
+
grouped.elements > 0 ? wf[perm].copy : wf[0...0].copy
|
|
701
865
|
end
|
|
702
866
|
|
|
703
867
|
# Map a per-segment index to the flat source address via the
|
|
@@ -735,6 +899,37 @@ class CASegmentIterator < CAIterator
|
|
|
735
899
|
out
|
|
736
900
|
end
|
|
737
901
|
|
|
902
|
+
# The value at the first or last present cell of each segment, UNDEF for a
|
|
903
|
+
# segment with none. Every segment's position is found at once: for each
|
|
904
|
+
# grouped slot, the nearest present slot at or after it (a running minimum
|
|
905
|
+
# taken from the end) or at or before it (a running maximum), read at the
|
|
906
|
+
# segment's first or last slot and kept when it falls inside the segment.
|
|
907
|
+
# A read-only Face cannot take UNDEF cell by cell, so its answers are
|
|
908
|
+
# collected segment by segment, as its other reductions are.
|
|
909
|
+
def edge_values (op)
|
|
910
|
+
n = grouped.elements
|
|
911
|
+
if n.zero? || (@value.face? && ! face_output?) || ! CArray.__fast_paths_enabled__
|
|
912
|
+
return per_segment(core_reduce_kind(op)) { |s| s.public_send(op) }
|
|
913
|
+
end
|
|
914
|
+
slot = CArray.int64(n).seq!
|
|
915
|
+
if grouped.has_mask?
|
|
916
|
+
slot[grouped.is_masked] = (op == :first) ? n : -1
|
|
917
|
+
end
|
|
918
|
+
lo, hi = @bounds[0...-1], @bounds[1..-1]
|
|
919
|
+
if op == :first
|
|
920
|
+
nearest = slot.reverse.cummin.reverse
|
|
921
|
+
at = nearest[lo.pmin(n - 1)]
|
|
922
|
+
else
|
|
923
|
+
nearest = slot.cummax
|
|
924
|
+
at = nearest[(hi - 1).pmax(0)]
|
|
925
|
+
end
|
|
926
|
+
found = at.ge(lo) & at.lt(hi)
|
|
927
|
+
out = grouped[at.pmin(n - 1).pmax(0)].copy
|
|
928
|
+
out[found.not] = UNDEF
|
|
929
|
+
# A reduction carries no attributes, a picked value included.
|
|
930
|
+
grouped.__send__(:drop_carried_attrs, out)
|
|
931
|
+
end
|
|
932
|
+
|
|
738
933
|
# The members of segment `c` as a CArray slice of the grouped copy. An empty
|
|
739
934
|
# segment (zero-width segment) yields the shared empty array — a zero-length
|
|
740
935
|
# slice cannot be taken directly, and an empty array carries the same reduction
|
|
@@ -754,10 +949,19 @@ class CASegmentIterator < CAIterator
|
|
|
754
949
|
MONOID_TYPES = %i[int8 uint8 int16 uint16 int32 uint32
|
|
755
950
|
int64 uint64 float32 float64].freeze
|
|
756
951
|
|
|
952
|
+
# Whether the C reduceat and scatter kernels read a value of +data_type+:
|
|
953
|
+
# a numeric one, with the fast paths on. They are a faster way to the
|
|
954
|
+
# core's answer for each group; a development build run with
|
|
955
|
+
# CARRAY_GENERAL_PATH set reduces every group by the core instead, which
|
|
956
|
+
# is how the two are compared.
|
|
957
|
+
def kernel_reads? (data_type = grouped.data_type)
|
|
958
|
+
MONOID_TYPES.include?(data_type) && CArray.__fast_paths_enabled__
|
|
959
|
+
end
|
|
960
|
+
|
|
757
961
|
def moments
|
|
758
962
|
return @moments if defined?(@moments)
|
|
759
963
|
@moments =
|
|
760
|
-
if
|
|
964
|
+
if kernel_reads?
|
|
761
965
|
dt = grouped.data_type
|
|
762
966
|
counts = CArray.int64(@k)
|
|
763
967
|
sums = CArray.float64(@k)
|
|
@@ -773,7 +977,7 @@ class CASegmentIterator < CAIterator
|
|
|
773
977
|
def arg_minmax
|
|
774
978
|
return @arg_minmax if defined?(@arg_minmax)
|
|
775
979
|
@arg_minmax =
|
|
776
|
-
if
|
|
980
|
+
if kernel_reads?
|
|
777
981
|
mn = CArray.int64(@k)
|
|
778
982
|
mx = CArray.int64(@k)
|
|
779
983
|
grouped.send(:__reduceat_argminmax__, @offsets, mn, mx)
|
|
@@ -787,7 +991,7 @@ class CASegmentIterator < CAIterator
|
|
|
787
991
|
def all_any
|
|
788
992
|
return @all_any if defined?(@all_any)
|
|
789
993
|
@all_any =
|
|
790
|
-
if grouped.data_type == CA_BOOLEAN
|
|
994
|
+
if grouped.data_type == CA_BOOLEAN && CArray.__fast_paths_enabled__
|
|
791
995
|
a = CArray.boolean(@k)
|
|
792
996
|
o = CArray.boolean(@k)
|
|
793
997
|
grouped.send(:__reduceat_all_any__, @offsets, a, o)
|
|
@@ -807,20 +1011,18 @@ class CASegmentIterator < CAIterator
|
|
|
807
1011
|
# per-fiber form -- and `accumulate(axis:)`, the one axis: member that needs
|
|
808
1012
|
# this, is exactly the case that would have found it missing.
|
|
809
1013
|
def core_reduce_type (op, *args)
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
CA_OBJECT : core_probe.public_send(op, *args, axis: 1).data_type
|
|
1014
|
+
kind = core_reduce_kind(op, *args)
|
|
1015
|
+
kind ? kind.data_type : CA_OBJECT
|
|
813
1016
|
end
|
|
814
1017
|
|
|
815
|
-
#
|
|
816
|
-
#
|
|
817
|
-
#
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
@value.reshape(@value.elements)[[0]].reshape(1, 1)
|
|
1018
|
+
# The core's answer itself for the one-cell probe, or nil for a Face with no
|
|
1019
|
+
# cell to probe. Its class is the one the per-segment answers come in, which
|
|
1020
|
+
# for a Face need not be the values' own (a CATime's spread is a CATimedelta).
|
|
1021
|
+
def core_reduce_kind (op, *args)
|
|
1022
|
+
memo = (@core_reduce_kind ||= {})
|
|
1023
|
+
return memo[[op, args]] if memo.key?([op, args])
|
|
1024
|
+
probe = CAIterator.core_probe(@value)
|
|
1025
|
+
memo[[op, args]] = probe && probe.public_send(op, *args, axis: 1)
|
|
824
1026
|
end
|
|
825
1027
|
|
|
826
1028
|
# Whether an output can be built by lifting one. A Face is filled by writing
|
|
@@ -838,15 +1040,22 @@ class CASegmentIterator < CAIterator
|
|
|
838
1040
|
# segment's members — the mask carries the "insufficient present data" contract
|
|
839
1041
|
# for free (an all-masked segment reduces like an empty one; identity-bearing
|
|
840
1042
|
# reductions return their identity, ratios return UNDEF).
|
|
841
|
-
def per_segment (
|
|
842
|
-
|
|
843
|
-
|
|
1043
|
+
def per_segment (kind)
|
|
1044
|
+
# a data type the values' own Face answers in names that Face
|
|
1045
|
+
kind = @value if ! kind.is_a?(CArray) && @value.face? && kind == @value.data_type
|
|
1046
|
+
data_type = kind.is_a?(CArray) ? kind.data_type : kind
|
|
1047
|
+
out = if kind.nil?
|
|
1048
|
+
CArray.new(CA_OBJECT, [@k])
|
|
1049
|
+
elsif ! (kind.is_a?(CArray) && kind.face?)
|
|
1050
|
+
bytes = if kind.is_a?(CArray) then kind.bytes
|
|
1051
|
+
elsif data_type == @value.data_type then @value.bytes
|
|
1052
|
+
else 0 end
|
|
844
1053
|
CArray.new(data_type, [@k], bytes: bytes)
|
|
845
|
-
elsif
|
|
846
|
-
# the core answered in
|
|
847
|
-
#
|
|
848
|
-
#
|
|
849
|
-
|
|
1054
|
+
elsif ! kind.read_only?
|
|
1055
|
+
# the core answered in a Face, so the output is one too:
|
|
1056
|
+
# CATime#min hands back a CATime::Element, which only a CATime
|
|
1057
|
+
# has anywhere to put
|
|
1058
|
+
kind.__send__(:blank_like, [@k])
|
|
850
1059
|
else
|
|
851
1060
|
# a read-only Face cannot be filled, so its answers are collected
|
|
852
1061
|
# as the surface objects they already are
|
|
@@ -860,6 +1069,10 @@ class CASegmentIterator < CAIterator
|
|
|
860
1069
|
|
|
861
1070
|
# ---- no axis: form -------------------------------------------------------
|
|
862
1071
|
|
|
1072
|
+
def axis_by_core (*)
|
|
1073
|
+
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
1074
|
+
end
|
|
1075
|
+
|
|
863
1076
|
def axis_by_masked_copy (*)
|
|
864
1077
|
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
865
1078
|
end
|
|
@@ -884,6 +1097,18 @@ class CASegmentIterator < CAIterator
|
|
|
884
1097
|
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
885
1098
|
end
|
|
886
1099
|
|
|
1100
|
+
def axis_lag (*)
|
|
1101
|
+
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
def axis_order (*)
|
|
1105
|
+
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
1106
|
+
end
|
|
1107
|
+
|
|
1108
|
+
def axis_hold (*)
|
|
1109
|
+
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
1110
|
+
end
|
|
1111
|
+
|
|
887
1112
|
def axis_map (*)
|
|
888
1113
|
raise NotImplementedError, "#{self.class} has no axis: form"
|
|
889
1114
|
end
|
|
@@ -931,37 +1156,47 @@ class CASegmentIterator < CAIterator
|
|
|
931
1156
|
(op == :count && (args.empty? || args[0].equal?(UNDEF)))
|
|
932
1157
|
end
|
|
933
1158
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
count_not_masked(**kw)
|
|
1159
|
+
CArray.send(:define_named_methods, self, REDUCTIONS, :reduce_counted, super_block: true)
|
|
1160
|
+
|
|
1161
|
+
# The segment reduction already leaves an empty segment as the core does,
|
|
1162
|
+
# so only a min_count asking for more is applied here. The reduction the
|
|
1163
|
+
# member is made of is the block (super).
|
|
1164
|
+
private def reduce_counted (op, args, kw)
|
|
1165
|
+
min_count = kw.delete(:min_count)
|
|
1166
|
+
fill_value = kw.delete(:fill_value)
|
|
1167
|
+
need = CArray.__present_cells_needed_per_result_cell__(op, min_count, 0)
|
|
1168
|
+
result = yield(*args, **kw)
|
|
1169
|
+
return result if need == 0 && fill_value.nil?
|
|
1170
|
+
if need > 0
|
|
1171
|
+
present = if op == :wsum || op == :wmean
|
|
1172
|
+
present_pair_count(args[0], kw[:axis])
|
|
1173
|
+
elsif ReductionCountAndFill.mask_count?(op, args)
|
|
1174
|
+
if kw[:axis]
|
|
1175
|
+
raise NotImplementedError,
|
|
1176
|
+
"#{self.class}##{op}(axis:, min_count:) is not implemented"
|
|
953
1177
|
end
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1178
|
+
elements
|
|
1179
|
+
else
|
|
1180
|
+
count_not_masked(**kw)
|
|
1181
|
+
end
|
|
1182
|
+
short = present.lt(need)
|
|
1183
|
+
end
|
|
1184
|
+
if short
|
|
1185
|
+
(result.is_a?(Array) ? result : [result]).each { |r| r[short] = UNDEF }
|
|
960
1186
|
end
|
|
1187
|
+
CArray.__fill_undefined_result_cells__(result, fill_value)
|
|
961
1188
|
end
|
|
962
1189
|
end
|
|
963
1190
|
prepend ReductionCountAndFill
|
|
964
1191
|
|
|
1192
|
+
|
|
1193
|
+
# lag and strip_mask(method:) give the source's values moved or held in
|
|
1194
|
+
# place, so the result shows the source's attributes, as CArray#lag and
|
|
1195
|
+
# CArray#strip_mask(method:) do.
|
|
1196
|
+
prepend(Module.new do
|
|
1197
|
+
def lag (*args, **keywords) = @value.__send__(:carry_attrs, super)
|
|
1198
|
+
def strip_mask (**keywords) = @value.__send__(:carry_attrs, super)
|
|
1199
|
+
end)
|
|
965
1200
|
end
|
|
966
1201
|
|
|
967
1202
|
|