carray 3.0.2 → 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 +568 -0
- data/README.md +6 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +220 -0
- data/ext/ca_axis_group.c +201 -123
- data/ext/ca_binop_dispatch.c +46 -52
- data/ext/ca_binop_dispatch.h +1 -1
- data/ext/ca_categorical_iterator.c +249 -183
- data/ext/ca_compare.c +7 -12
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +38 -23
- data/ext/ca_kernel_iterator.c +1765 -2076
- data/ext/ca_kernel_iterator.h +584 -566
- data/ext/ca_monop_dispatch.c +86 -63
- data/ext/ca_monop_dispatch.h +5 -0
- data/ext/ca_obj_array.c +223 -56
- data/ext/ca_obj_bincmp.c +172 -138
- data/ext/ca_obj_binop.c +116 -230
- data/ext/ca_obj_bitarray.c +40 -26
- data/ext/ca_obj_bitfield.c +100 -68
- data/ext/ca_obj_block.c +19 -9
- data/ext/ca_obj_byte_swap.c +45 -46
- data/ext/ca_obj_const_string.c +269 -44
- data/ext/ca_obj_face.c +612 -89
- data/ext/ca_obj_face.h +111 -47
- data/ext/ca_obj_fake.c +36 -79
- data/ext/ca_obj_farray.c +7 -3
- data/ext/ca_obj_field.c +33 -8
- data/ext/ca_obj_fixlen_string.c +23 -24
- data/ext/ca_obj_grid.c +541 -210
- data/ext/ca_obj_meld.c +331 -111
- data/ext/ca_obj_moncmp.c +80 -65
- data/ext/ca_obj_monop.c +177 -149
- data/ext/ca_obj_object.c +664 -248
- data/ext/ca_obj_record.c +63 -23
- data/ext/ca_obj_reduce.c +18 -16
- data/ext/ca_obj_refer.c +140 -58
- data/ext/ca_obj_remap.c +85 -24
- data/ext/ca_obj_repeat.c +24 -9
- data/ext/ca_obj_roll.c +121 -16
- data/ext/ca_obj_select.c +61 -58
- data/ext/ca_obj_select_axis.c +73 -107
- data/ext/ca_obj_shift.c +32 -102
- data/ext/ca_obj_stack.c +372 -122
- data/ext/ca_obj_stride.c +348 -140
- data/ext/ca_obj_string.c +88 -22
- data/ext/ca_obj_tile.c +38 -21
- data/ext/ca_obj_time.c +45 -31
- data/ext/ca_obj_timedelta.c +43 -29
- data/ext/ca_obj_transpose.c +17 -6
- data/ext/ca_obj_triop.c +125 -141
- data/ext/ca_obj_window.c +330 -44
- data/ext/ca_op_ipower.c +24 -11
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +104 -81
- data/ext/ca_sweep_engine.h +7 -20
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +542 -0
- data/ext/carray.h +241 -92
- data/ext/carray_access.c +608 -276
- data/ext/carray_address_basis.c +494 -97
- data/ext/carray_attribute.c +129 -4
- data/ext/carray_bincount.c +39 -7
- data/ext/carray_broadcast.c +56 -19
- data/ext/carray_build_flags.h +1 -1
- data/ext/carray_call_cfunc.c +108 -28
- data/ext/carray_cast.c +1576 -223
- data/ext/carray_cast_func.rb +77 -24
- data/ext/carray_class.c +4 -3
- data/ext/carray_conversion.c +208 -75
- data/ext/carray_copy.c +82 -24
- data/ext/carray_core.c +646 -162
- data/ext/carray_count.c +171 -88
- data/ext/carray_element.c +94 -60
- data/ext/carray_factorize.c +670 -324
- data/ext/carray_generate.c +260 -83
- data/ext/carray_histogram.c +209 -106
- data/ext/carray_hold.c +14 -16
- data/ext/carray_index_classifier.c +422 -243
- data/ext/carray_internal.h +692 -3
- data/ext/carray_kernels_bincmp.c +7 -0
- data/ext/carray_kernels_binop.c +487 -120
- data/ext/carray_kernels_init.c +653 -50
- data/ext/carray_kernels_moncmp.c +15 -8
- data/ext/carray_kernels_monop.c +99 -43
- data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
- data/ext/carray_kernels_reduce_boolean.c +112 -36
- data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
- data/ext/carray_kernels_reduce_extreme.c +3032 -2892
- data/ext/carray_kernels_reduce_variance.c +439 -500
- data/ext/carray_kernels_scan.c +1097 -488
- data/ext/carray_kernels_search.c +3787 -2176
- data/ext/carray_kernels_sort.c +1200 -920
- data/ext/carray_kernels_triop.c +479 -7
- data/ext/carray_key_windows.c +216 -0
- data/ext/carray_lazy.c +967 -153
- data/ext/carray_loop.c +81 -65
- data/ext/carray_mask.c +910 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +299 -100
- data/ext/carray_memory_view.c +872 -758
- data/ext/carray_operator.c +1171 -364
- data/ext/carray_order.c +505 -133
- data/ext/carray_partition.c +112 -65
- data/ext/carray_random.c +327 -239
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +346 -78
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +249 -86
- data/ext/carray_sort.c +224 -227
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +118 -39
- data/ext/carray_test.c +193 -69
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +579 -113
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +19 -4
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1202 -1014
- data/ext/ruby_carray.c +17 -5
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +160 -17
- data/lib/carray/autoload_carray.rb +23 -27
- data/lib/carray/autoload_method_extension.rb +20 -5
- data/lib/carray/axis_group.rb +360 -63
- data/lib/carray/basics.rb +162 -28
- data/lib/carray/bincount_nd.rb +18 -2
- data/lib/carray/block_iterator.rb +231 -58
- data/lib/carray/boolean_reduce.rb +34 -42
- data/lib/carray/categorical.rb +242 -52
- data/lib/carray/categorical_iterator.rb +437 -827
- data/lib/carray/complex.rb +41 -34
- data/lib/carray/conditional.rb +150 -14
- data/lib/carray/const_string.rb +59 -4
- 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/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +82 -7
- data/lib/carray/frame/csv_parser.rb +688 -87
- data/lib/carray/frame/describe.rb +80 -0
- data/lib/carray/frame/frame.rb +369 -45
- data/lib/carray/frame/group.rb +631 -90
- data/lib/carray/frame/io.rb +506 -79
- data/lib/carray/frame/join.rb +411 -62
- data/lib/carray/frame/records.rb +116 -19
- data/lib/carray/frame/reshape.rb +455 -0
- data/lib/carray/frame/sort.rb +30 -18
- data/lib/carray/frame/verbs.rb +868 -61
- data/lib/carray/frame/windows.rb +117 -0
- data/lib/carray/frame.rb +4 -0
- data/lib/carray/fuse_source.rb +118 -2
- data/lib/carray/fusion.rb +257 -42
- data/lib/carray/histogram.rb +6 -1
- data/lib/carray/inspect.rb +54 -13
- data/lib/carray/iterator.rb +99 -9
- data/lib/carray/key_windows.rb +620 -0
- data/lib/carray/lazy.rb +216 -193
- data/lib/carray/mask_gap_fill.rb +112 -92
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +52 -21
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bin.rb +26 -18
- data/lib/carray/methods/bincount.rb +18 -2
- data/lib/carray/methods/bit_string.rb +15 -4
- data/lib/carray/methods/choose.rb +16 -3
- data/lib/carray/methods/composition.rb +18 -10
- data/lib/carray/methods/factorize.rb +2 -2
- data/lib/carray/methods/gather_nd.rb +25 -5
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_between.rb +59 -0
- data/lib/carray/methods/is_in.rb +140 -10
- data/lib/carray/methods/join.rb +2 -7
- data/lib/carray/methods/lag.rb +49 -0
- data/lib/carray/methods/locate_addr.rb +182 -45
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +11 -6
- data/lib/carray/methods/repeat.rb +1 -20
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +11 -2
- data/lib/carray/methods/value_counts.rb +4 -11
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +3 -0
- data/lib/carray/runtime.rb +65 -17
- data/lib/carray/segment_iterator.rb +1221 -0
- data/lib/carray/serialize.rb +214 -30
- data/lib/carray/slab_iterator.rb +101 -27
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string.rb +105 -5
- data/lib/carray/string_operation_extension.rb +12 -4
- data/lib/carray/struct.rb +137 -58
- data/lib/carray/struct_builder.rb +124 -17
- data/lib/carray/time.rb +1222 -186
- data/lib/carray/window_iterator.rb +242 -81
- data/lib/carray.rb +9 -2
- data/yard-stubs/ca_obj_array.rb +4 -4
- data/yard-stubs/ca_obj_bitarray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +23 -12
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +5 -1
- data/yard-stubs/ca_obj_grid.rb +21 -10
- data/yard-stubs/ca_obj_refer.rb +5 -4
- data/yard-stubs/ca_obj_roll.rb +2 -3
- data/yard-stubs/ca_obj_shift.rb +5 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_tile.rb +1 -1
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +9 -5
- data/yard-stubs/carray_access.rb +11 -5
- data/yard-stubs/carray_attribute.rb +0 -5
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_cast.rb +88 -43
- data/yard-stubs/carray_conversion.rb +15 -2
- data/yard-stubs/carray_copy.rb +12 -5
- data/yard-stubs/carray_core.rb +3 -3
- data/yard-stubs/carray_count.rb +9 -0
- data/yard-stubs/carray_element.rb +10 -4
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_lazy.rb +5 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +60 -12
- data/yard-stubs/carray_math.rb +52 -14
- data/yard-stubs/carray_median_percentile.rb +15 -8
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +61 -29
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +21 -9
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +10 -2
- data/yard-stubs/carray_sort.rb +49 -8
- data/yard-stubs/carray_test.rb +15 -3
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -5
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_kernels_map.c +0 -3467
- data/lib/carray/arrow_tensor.rb +0 -401
- data/lib/carray/table.rb +0 -108
data/lib/carray/axis_group.rb
CHANGED
|
@@ -213,6 +213,25 @@ class AxisGroup
|
|
|
213
213
|
end
|
|
214
214
|
end
|
|
215
215
|
|
|
216
|
+
# The keywords a group reduction takes besides axis:. Checked before the
|
|
217
|
+
# work, so an unknown keyword is refused rather than dropped.
|
|
218
|
+
REDUCE_KEYWORDS = [:axis, :min_count, :fill_value].freeze
|
|
219
|
+
|
|
220
|
+
# all / any take axis: only, as the core ones do.
|
|
221
|
+
LOGICAL_REDUCE_KEYWORDS = [:axis].freeze
|
|
222
|
+
|
|
223
|
+
# A boolean array carrying value's mask: what a count reads, for a value
|
|
224
|
+
# whose payload the group kernel cannot read. Called from C.
|
|
225
|
+
private_class_method def self.count_stand_in (value)
|
|
226
|
+
return value if value.boolean? || value.integer? || value.float?
|
|
227
|
+
stand_in = CArray.boolean(*value.shape)
|
|
228
|
+
stand_in.mask = value.mask if value.has_mask?
|
|
229
|
+
stand_in
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# A count of the mask reads every cell of the group, as the core's does.
|
|
233
|
+
MASK_COUNTS = [:count, :count_not_masked, :count_masked].freeze
|
|
234
|
+
|
|
216
235
|
# Parse an axis: spec into [has_group, fused_band_slot_positions].
|
|
217
236
|
def self.parse_axis (axis)
|
|
218
237
|
has_group = false
|
|
@@ -332,58 +351,109 @@ class CAGroupIterator
|
|
|
332
351
|
# -- arrived as "wrong number of arguments", which reads as a method that
|
|
333
352
|
# does not take one rather than one whose engine has no such fold. Both are
|
|
334
353
|
# composed here instead, out of folds the engine does have.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
#
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
354
|
+
# They are in a module in front of the engine's own count, all and any,
|
|
355
|
+
# which they reach with super; so are the reductions below that choose
|
|
356
|
+
# between the engine and the core.
|
|
357
|
+
module ComposedFolds
|
|
358
|
+
|
|
359
|
+
# @overload count
|
|
360
|
+
# Per-group count of present (non-masked) cells.
|
|
361
|
+
# @overload count(UNDEF)
|
|
362
|
+
# Per-group count of masked cells, as {#count_masked}.
|
|
363
|
+
# @overload count(v)
|
|
364
|
+
# Per-group count of cells equal to `v`.
|
|
365
|
+
# @return [CArray]
|
|
366
|
+
def count (*args, **kw)
|
|
367
|
+
if args.empty?
|
|
368
|
+
return core_counts?(kw) ? reduce_by_core(:count, kw) : super(**kw)
|
|
369
|
+
end
|
|
370
|
+
if args.size > 1
|
|
371
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..1)"
|
|
372
|
+
end
|
|
373
|
+
v = args.first
|
|
374
|
+
return count_masked(**kw) if v.equal?(UNDEF)
|
|
375
|
+
# eq leaves a masked cell UNDEF, so the sum leaves it out and its
|
|
376
|
+
# min_count: counts the present cells, as the core count(v) does
|
|
377
|
+
regroup(value.eq(v).int64, spec).sum(**kw).int64
|
|
348
378
|
end
|
|
349
|
-
v = args.first
|
|
350
|
-
return count_masked(**kw) if v.equal?(UNDEF)
|
|
351
|
-
# a masked cell equals nothing, and eq marks it UNDEF; drop that to false
|
|
352
|
-
# so it is simply not counted
|
|
353
|
-
hit = value.eq(v)
|
|
354
|
-
hit = hit.strip_mask(false) if hit.has_mask?
|
|
355
|
-
self.class.__build__(hit.int64, spec).sum(**kw).int64
|
|
356
|
-
end
|
|
357
379
|
|
|
358
|
-
|
|
359
|
-
|
|
380
|
+
# @overload all
|
|
381
|
+
# Per-group `all` over a boolean payload.
|
|
382
|
+
# @return [CArray]
|
|
383
|
+
def all (**kw)
|
|
384
|
+
boolean_payload!(:all)
|
|
385
|
+
core_counts?(kw) ? reduce_by_core(:all, kw) : super(**kw)
|
|
386
|
+
end
|
|
360
387
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
388
|
+
# @overload any
|
|
389
|
+
# Per-group `any` over a boolean payload.
|
|
390
|
+
# @return [CArray]
|
|
391
|
+
def any (**kw)
|
|
392
|
+
boolean_payload!(:any)
|
|
393
|
+
core_counts?(kw) ? reduce_by_core(:any, kw) : super(**kw)
|
|
394
|
+
end
|
|
368
395
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
396
|
+
# @overload count_not_masked
|
|
397
|
+
# Per-group count of present (non-masked) cells.
|
|
398
|
+
# @return [CArray]
|
|
399
|
+
def count_not_masked (**kw)
|
|
400
|
+
core_counts?(kw) ? reduce_by_core(:count_not_masked, kw) : super(**kw)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
private
|
|
404
|
+
|
|
405
|
+
# Whether a count or all / any of each group is taken from the core, as
|
|
406
|
+
# the other reductions of a payload the engine does not read are. The
|
|
407
|
+
# engine answers these for every payload; with the fast paths off (a
|
|
408
|
+
# development build run with CARRAY_GENERAL_PATH set) the core does --
|
|
409
|
+
# except for a band folded into the count, which has no route but the
|
|
410
|
+
# engine.
|
|
411
|
+
def core_counts? (kw)
|
|
412
|
+
return false if CArray.__fast_paths_enabled__
|
|
413
|
+
has_group, fused = AxisGroup.parse_axis(kw[:axis])
|
|
414
|
+
has_group && fused.empty?
|
|
415
|
+
end
|
|
375
416
|
end
|
|
417
|
+
prepend ComposedFolds
|
|
376
418
|
|
|
377
419
|
# Per-group classified cell count (mask-independent) = count on the
|
|
378
420
|
# mask-stripped value, so every classified cell is counted regardless of the
|
|
379
421
|
# value mask (unlike count / count_not_masked, which count present cells).
|
|
380
422
|
def elements (**kw)
|
|
381
|
-
|
|
423
|
+
regroup(value.value, spec).count(**kw)
|
|
382
424
|
end
|
|
383
425
|
|
|
384
426
|
# Per-group count of value-masked cells = elements - count_not_masked.
|
|
385
427
|
def count_masked (**kw)
|
|
386
|
-
|
|
428
|
+
has_group, = AxisGroup.parse_axis(kw[:axis])
|
|
429
|
+
return value.count_masked(**kw) unless has_group
|
|
430
|
+
check_reduce_keywords(:count_masked, kw)
|
|
431
|
+
plain = { axis: kw[:axis] }
|
|
432
|
+
finish_reduction(:count_masked, kw,
|
|
433
|
+
elements(**plain) - count_not_masked(**plain))
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# The scatter kernel reads an integer or float payload, and answers there
|
|
437
|
+
# as the core does. Any other payload -- boolean, complex, object, a Face --
|
|
438
|
+
# is reduced group by group by the core, through the composite categorical,
|
|
439
|
+
# so the data type, the Face and a refusal are the core's: a boolean's sum
|
|
440
|
+
# is uint64, a time column's minimum a time, its sum refused.
|
|
441
|
+
CArray.send(:define_named_methods, ComposedFolds,
|
|
442
|
+
[:sum, :accumulate, :prod, :mean, :min, :max,
|
|
443
|
+
:variance, :stddev, :variancep, :stddevp],
|
|
444
|
+
:scatter_or_core, super_block: true)
|
|
445
|
+
|
|
446
|
+
# The scatter kernel is the block (super).
|
|
447
|
+
private def scatter_or_core (op, args, kw)
|
|
448
|
+
unless args.empty?
|
|
449
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0)"
|
|
450
|
+
end
|
|
451
|
+
has_group, fused = AxisGroup.parse_axis(kw[:axis])
|
|
452
|
+
if ! has_group || kernel_reads_value?(fused)
|
|
453
|
+
yield(**kw)
|
|
454
|
+
else
|
|
455
|
+
reduce_by_core(op, kw)
|
|
456
|
+
end
|
|
387
457
|
end
|
|
388
458
|
|
|
389
459
|
# Per-group [min, max] pair (matching CArray#minmax).
|
|
@@ -418,7 +488,7 @@ class CAGroupIterator
|
|
|
418
488
|
# value|weight mask, so masked cells drop out. Empty group -> 0.0 (identity).
|
|
419
489
|
# No weighted kernel needed -- it is a plain group-sum of a derived array.
|
|
420
490
|
def wsum (weights, **kw)
|
|
421
|
-
|
|
491
|
+
regroup(value * weights, spec).sum(**kw)
|
|
422
492
|
end
|
|
423
493
|
|
|
424
494
|
# Weighted mean = Sum(v*w) / Sum(w), both over the combined present-set. The
|
|
@@ -427,13 +497,21 @@ class CAGroupIterator
|
|
|
427
497
|
# (no present pair) is UNDEF (matching CArray#wmean); a present group whose
|
|
428
498
|
# weights sum to zero yields NaN/Inf (core's 0/0).
|
|
429
499
|
def wmean (weights, **kw)
|
|
500
|
+
has_group, = AxisGroup.parse_axis(kw[:axis])
|
|
501
|
+
return value.wmean(weights, **kw) unless has_group
|
|
502
|
+
check_reduce_keywords(:wmean, kw)
|
|
503
|
+
plain = { axis: kw[:axis] }
|
|
430
504
|
prod = value * weights
|
|
431
|
-
num =
|
|
432
|
-
den =
|
|
433
|
-
cnt =
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
505
|
+
num = regroup(prod, spec).sum(**plain)
|
|
506
|
+
den = regroup(value * 0 + weights, spec).sum(**plain)
|
|
507
|
+
cnt = regroup(prod, spec).count_not_masked(**plain)
|
|
508
|
+
# No present pair is UNDEF (an empty group), set on the denominator so
|
|
509
|
+
# the division never meets an empty group's 0 / 0.
|
|
510
|
+
den = den.copy
|
|
511
|
+
den[cnt.eq(0)] = UNDEF
|
|
512
|
+
# min_count: counts the present pairs, as the core wmean does.
|
|
513
|
+
pairs = regroup(prod, spec)
|
|
514
|
+
pairs.finish_reduction(:wmean, kw, num / den)
|
|
437
515
|
end
|
|
438
516
|
|
|
439
517
|
# ---- segment scan: within-group running statistics ---------------------
|
|
@@ -444,7 +522,7 @@ class CAGroupIterator
|
|
|
444
522
|
# in row-major position order along the grouped axes (per band). Excluded /
|
|
445
523
|
# source-masked cells are UNDEF (they join no running total). All five route
|
|
446
524
|
# through the shared scan_op dispatcher, driving the fused C kernel
|
|
447
|
-
#
|
|
525
|
+
# __scan_by_axis_groups__ (ext/ca_axis_group.c) with peak O(k) extra. A scan
|
|
448
526
|
# cannot fold a band into the statistic (that would collapse an axis), so it
|
|
449
527
|
# accepts only axis: :group; without :group each delegates to the value's
|
|
450
528
|
# same-named scan.
|
|
@@ -493,6 +571,59 @@ class CAGroupIterator
|
|
|
493
571
|
scan_op(:cumcount, kw)
|
|
494
572
|
end
|
|
495
573
|
|
|
574
|
+
# @overload lag(n = 1, axis: :group)
|
|
575
|
+
# Per-group value `n` steps earlier, counting a group's members in
|
|
576
|
+
# row-major position order along the grouped axes (per band), as the
|
|
577
|
+
# scans do; UNDEF where the group has none, so a lag never reaches
|
|
578
|
+
# another group. Without :group it is the value's own `lag` along an
|
|
579
|
+
# axis. A negative `n` looks ahead.
|
|
580
|
+
# @return [CArray] source-shaped
|
|
581
|
+
def lag (n = 1, axis: nil)
|
|
582
|
+
has_group, fused = AxisGroup.parse_axis(axis)
|
|
583
|
+
return value.lag(n, axis: axis) unless has_group
|
|
584
|
+
unless fused.empty?
|
|
585
|
+
raise ArgumentError,
|
|
586
|
+
"axis_group lag: folding a band into a lag (axis: [:group, k]) " \
|
|
587
|
+
"is not supported; a lag preserves the source shape"
|
|
588
|
+
end
|
|
589
|
+
scan_by_core(:lag, n)
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# @overload strip_mask(method:, axis: :group)
|
|
593
|
+
# Per-group fill of the masked cells, counting a group's members in
|
|
594
|
+
# row-major position order along the grouped axes (per band), as the
|
|
595
|
+
# scans do: `:forward` / `:ffill` or `:backward` / `:bfill`, never across
|
|
596
|
+
# into another group. Without :group it is the value's own
|
|
597
|
+
# `strip_mask(method:, axis:)`.
|
|
598
|
+
# @return [CArray] source-shaped
|
|
599
|
+
def strip_mask (method:, axis: nil)
|
|
600
|
+
has_group, fused = AxisGroup.parse_axis(axis)
|
|
601
|
+
return value.strip_mask(method: method, axis: axis) unless has_group
|
|
602
|
+
CAIterator.hold_backward?(method)
|
|
603
|
+
unless fused.empty?
|
|
604
|
+
raise ArgumentError,
|
|
605
|
+
"axis_group strip_mask: folding a band into a fill (axis: [:group, k]) " \
|
|
606
|
+
"is not supported; a fill preserves the source shape"
|
|
607
|
+
end
|
|
608
|
+
scan_by_core(:strip_mask, method: method)
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
# @overload order(axis: :group, descending: false, method: :ordinal, kind: :quick)
|
|
612
|
+
# Per-group rank of each cell among its group's members (int64, `0` the
|
|
613
|
+
# smallest; per band), as `CArray#order` ranks them; a cell in no group is
|
|
614
|
+
# UNDEF. Without :group it is the value's own `order`.
|
|
615
|
+
# @return [CArray] source-shaped
|
|
616
|
+
def order (axis: nil, **kw)
|
|
617
|
+
has_group, fused = AxisGroup.parse_axis(axis)
|
|
618
|
+
return value.order(axis: axis, **kw) unless has_group
|
|
619
|
+
unless fused.empty?
|
|
620
|
+
raise ArgumentError,
|
|
621
|
+
"axis_group order: folding a band into an order (axis: [:group, k]) " \
|
|
622
|
+
"is not supported; an order preserves the source shape"
|
|
623
|
+
end
|
|
624
|
+
scan_by_core(:order, **kw)
|
|
625
|
+
end
|
|
626
|
+
|
|
496
627
|
# ---- materialize path: order statistics + generic iteration ------------
|
|
497
628
|
#
|
|
498
629
|
# These need every member of a group held together, so (unlike the scatter
|
|
@@ -534,6 +665,41 @@ class CAGroupIterator
|
|
|
534
665
|
order_stat(:quantile, [], kw)
|
|
535
666
|
end
|
|
536
667
|
|
|
668
|
+
# @overload first(axis: :group)
|
|
669
|
+
# Per-group first present value, in row-major position order along the
|
|
670
|
+
# grouped axes (per band); UNDEF for a group with none. Without :group it
|
|
671
|
+
# is the value's own `first`.
|
|
672
|
+
# @return [CArray]
|
|
673
|
+
# @overload last(axis: :group)
|
|
674
|
+
# Per-group last present value; UNDEF for a group with none.
|
|
675
|
+
# @return [CArray]
|
|
676
|
+
# @overload nunique(axis: :group)
|
|
677
|
+
# Per-group number of distinct present values (int64); 0 for a group with
|
|
678
|
+
# none.
|
|
679
|
+
# @return [CArray]
|
|
680
|
+
#
|
|
681
|
+
# These take no `min_count:` or `fill_value:`, as the core ones do not. A
|
|
682
|
+
# group is gathered whole, so folding a band into one (axis: [:group, k]) is
|
|
683
|
+
# not supported, as for the order statistics.
|
|
684
|
+
CArray.send(:define_named_methods, self, [:first, :last, :nunique], :group_position)
|
|
685
|
+
|
|
686
|
+
private def group_position (op, **kw)
|
|
687
|
+
has_group, fused = AxisGroup.parse_axis(kw[:axis])
|
|
688
|
+
return value.public_send(op, **kw) unless has_group
|
|
689
|
+
unless fused.empty?
|
|
690
|
+
raise NotImplementedError,
|
|
691
|
+
"axis_group: folding a band into #{op} (axis: [:group, k]) is not " \
|
|
692
|
+
"supported; #{op} gathers every member of a group"
|
|
693
|
+
end
|
|
694
|
+
unknown = kw.keys - [:axis]
|
|
695
|
+
unless unknown.empty?
|
|
696
|
+
raise ArgumentError,
|
|
697
|
+
"#{op}: unknown keyword#{unknown.size > 1 ? "s" : ""}: " +
|
|
698
|
+
unknown.map(&:inspect).join(", ")
|
|
699
|
+
end
|
|
700
|
+
composite_order(op, [])
|
|
701
|
+
end
|
|
702
|
+
|
|
537
703
|
# @overload sort_addr
|
|
538
704
|
# Per-group sorted flat source addresses (group-major). Any grouping (single
|
|
539
705
|
# or composite group slots, rank-1 or rank-N, flat or band-preserving) is
|
|
@@ -569,12 +735,13 @@ class CAGroupIterator
|
|
|
569
735
|
raise LocalJumpError, "no block given (yield)" unless block
|
|
570
736
|
ccat, _kd, gslots, bslots, gaxes = composite_layout
|
|
571
737
|
return value.group_by_category(ccat).map(data_type: data_type, &block) if bslots.empty?
|
|
572
|
-
|
|
573
|
-
out = CArray.new(dt, value.shape)
|
|
738
|
+
out = nil
|
|
574
739
|
each_band_block(ccat, gslots, bslots, gaxes) do |val_idx, _oi, _co, gi|
|
|
575
|
-
|
|
740
|
+
result = gi.map(data_type: data_type, &block)
|
|
741
|
+
out ||= blank_of(result, value.shape)
|
|
742
|
+
out[*val_idx] = result
|
|
576
743
|
end
|
|
577
|
-
out
|
|
744
|
+
out || (data_type ? CArray.new(data_type, value.shape) : blank_of(value, value.shape))
|
|
578
745
|
end
|
|
579
746
|
|
|
580
747
|
# @overload reduce { |members| ... }
|
|
@@ -599,8 +766,50 @@ class CAGroupIterator
|
|
|
599
766
|
out
|
|
600
767
|
end
|
|
601
768
|
|
|
769
|
+
protected
|
|
770
|
+
|
|
771
|
+
# min_count: masks a group with fewer contributing cells (present cells,
|
|
772
|
+
# or every cell for a count of the mask); fill_value: then fills the masked
|
|
773
|
+
# cells -- as a core reduction does with each. Protected: wmean finishes
|
|
774
|
+
# through the iterator over its products.
|
|
775
|
+
def finish_reduction (op, kw, result)
|
|
776
|
+
need = CArray.__present_cells_needed_per_result_cell__(op, kw[:min_count], 0)
|
|
777
|
+
if need > 0
|
|
778
|
+
counts = if AxisGroup::MASK_COUNTS.include?(op.to_sym)
|
|
779
|
+
elements(axis: kw[:axis])
|
|
780
|
+
else
|
|
781
|
+
count_not_masked(axis: kw[:axis])
|
|
782
|
+
end
|
|
783
|
+
if result.is_a?(CArray)
|
|
784
|
+
result[counts.lt(need)] = UNDEF
|
|
785
|
+
elsif counts < need
|
|
786
|
+
result = UNDEF
|
|
787
|
+
end
|
|
788
|
+
end
|
|
789
|
+
CArray.__fill_undefined_result_cells__(result, kw[:fill_value])
|
|
790
|
+
end
|
|
791
|
+
|
|
602
792
|
private
|
|
603
793
|
|
|
794
|
+
# An unknown keyword is refused before the work is done.
|
|
795
|
+
def check_reduce_keywords (op, kw)
|
|
796
|
+
allowed = [:all, :any].include?(op.to_sym) ? AxisGroup::LOGICAL_REDUCE_KEYWORDS : AxisGroup::REDUCE_KEYWORDS
|
|
797
|
+
unknown = kw.keys - allowed
|
|
798
|
+
unless unknown.empty?
|
|
799
|
+
raise ArgumentError,
|
|
800
|
+
"#{op}: unknown keyword#{unknown.size > 1 ? "s" : ""}: " +
|
|
801
|
+
unknown.map(&:inspect).join(", ")
|
|
802
|
+
end
|
|
803
|
+
CArray.__present_cells_needed_per_result_cell__(op, kw[:min_count], 0)
|
|
804
|
+
nil
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
# The same grouping over another value.
|
|
808
|
+
def regroup (value, spec)
|
|
809
|
+
self.class.send(:build_for, value, spec)
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
|
|
604
813
|
# Build the composite categorical over the group block (the sub-array spanning
|
|
605
814
|
# every group axis). Each group slot -- rank-1 or rank-N -- contributes its
|
|
606
815
|
# per-axis codes; the slot codes are broadcast into the group-block rank (size
|
|
@@ -622,7 +831,8 @@ class CAGroupIterator
|
|
|
622
831
|
# askable. Asking a one-cell array of the same data type lets the core's own
|
|
623
832
|
# refusal through, unworded by us.
|
|
624
833
|
def boolean_payload! (op)
|
|
625
|
-
|
|
834
|
+
probe = CAIterator.core_probe(value)
|
|
835
|
+
probe.public_send(op) if probe
|
|
626
836
|
nil
|
|
627
837
|
end
|
|
628
838
|
|
|
@@ -649,6 +859,13 @@ class CAGroupIterator
|
|
|
649
859
|
[ccat, kdims, gslots, bslots, gaxes]
|
|
650
860
|
end
|
|
651
861
|
|
|
862
|
+
# With no band axis the group block is the whole value, grouped in one
|
|
863
|
+
# pass. With the fast paths off it goes through the band loop instead,
|
|
864
|
+
# which then has one position.
|
|
865
|
+
def whole_block? (bslots)
|
|
866
|
+
bslots.empty? && CArray.__fast_paths_enabled__
|
|
867
|
+
end
|
|
868
|
+
|
|
652
869
|
# Iterate the band positions of a band-preserving grouping. For each band
|
|
653
870
|
# position it yields the group block grouped by the composite categorical,
|
|
654
871
|
# together with two indices: val_idx (source order -- nil across the group
|
|
@@ -696,8 +913,74 @@ class CAGroupIterator
|
|
|
696
913
|
"axis_group scan: folding a band into a scan (axis: [:group, k]) " \
|
|
697
914
|
"is not supported; a scan preserves the source shape"
|
|
698
915
|
end
|
|
916
|
+
return scan_by_core(op) unless kernel_scans?(op)
|
|
699
917
|
group_axes, bundles, = spec.reduce_plan([])
|
|
700
|
-
value.
|
|
918
|
+
value.__scan_by_axis_groups__(group_axes, bundles, op)
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
# Whether the scatter kernels read this payload: an integer or a float,
|
|
922
|
+
# not a Face. They answer there in the core's data types. With the fast
|
|
923
|
+
# paths off (a development build run with CARRAY_GENERAL_PATH set) each
|
|
924
|
+
# group is reduced by the core instead -- except a band folded into the
|
|
925
|
+
# statistic (+fused+), which has no route but the kernel.
|
|
926
|
+
def kernel_reads_value? (fused = [])
|
|
927
|
+
return false if value.face? || ! (value.integer? || value.float?)
|
|
928
|
+
CArray.__fast_paths_enabled__ || ! fused.empty?
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
# Whether the scan kernel gives the core's answer: it reads an integer,
|
|
932
|
+
# float, boolean or object payload (not a complex, not a Face), and answers cumsum / cumprod in
|
|
933
|
+
# float64 (an object in object), cummax / cummin in the payload's data type
|
|
934
|
+
# and cumcount in int64 -- not the core's answer for a boolean's running sum.
|
|
935
|
+
def kernel_scans? (op)
|
|
936
|
+
return false unless CArray.__fast_paths_enabled__
|
|
937
|
+
return false if value.face? || value.complex?
|
|
938
|
+
return true if value.object? || op == :cumcount
|
|
939
|
+
core = CAIterator.core_probe(value).public_send(op).data_type
|
|
940
|
+
kernel = [:cumsum, :cumprod].include?(op) ? CA_FLOAT64 : value.data_type
|
|
941
|
+
core == kernel
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
# A scan the kernel does not answer as the core does: each group block is
|
|
945
|
+
# grouped through the composite categorical, whose scans are the core's.
|
|
946
|
+
def scan_by_core (op, *args, **kw)
|
|
947
|
+
ccat, _kd, gslots, bslots, gaxes = composite_layout
|
|
948
|
+
return value.group_by_category(ccat).public_send(op, *args, **kw) if whole_block?(bslots)
|
|
949
|
+
out = nil
|
|
950
|
+
each_band_block(ccat, gslots, bslots, gaxes) do |val_idx, _oi, _co, gi|
|
|
951
|
+
result = gi.public_send(op, *args, **kw)
|
|
952
|
+
out ||= blank_of(result, value.shape)
|
|
953
|
+
out[*val_idx] = result
|
|
954
|
+
end
|
|
955
|
+
out || blank_of(value, value.shape)
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
# A reduction of a payload the scatter kernel does not read, group by
|
|
959
|
+
# group through the composite categorical. Folding a band into the
|
|
960
|
+
# statistic needs the kernel.
|
|
961
|
+
def reduce_by_core (op, kw)
|
|
962
|
+
_, fused = AxisGroup.parse_axis(kw[:axis])
|
|
963
|
+
unless fused.empty?
|
|
964
|
+
raise ArgumentError,
|
|
965
|
+
"axis_group #{op}: folding a band into the statistic " \
|
|
966
|
+
"(axis: [:group, k]) is not supported for a #{value.class} of " \
|
|
967
|
+
"data type #{value.data_type_name}"
|
|
968
|
+
end
|
|
969
|
+
check_reduce_keywords(op, kw)
|
|
970
|
+
finish_reduction(op, kw, composite_order(op, []))
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
# An array of +shape+ of the same kind as +model+ -- its data type, or its
|
|
974
|
+
# Face -- with every cell UNDEF. A read-only Face cannot be written cell by
|
|
975
|
+
# cell, so its values are held in an object array.
|
|
976
|
+
def blank_of (model, shape)
|
|
977
|
+
out = if model.face? && model.read_only?
|
|
978
|
+
CArray.new(CA_OBJECT, shape)
|
|
979
|
+
else
|
|
980
|
+
model.__send__(:blank_like, shape)
|
|
981
|
+
end
|
|
982
|
+
out[] = UNDEF
|
|
983
|
+
out
|
|
701
984
|
end
|
|
702
985
|
|
|
703
986
|
# Dispatch an order statistic (median / percentile / quantile). No :group ->
|
|
@@ -707,13 +990,19 @@ class CAGroupIterator
|
|
|
707
990
|
# operation, and remains a follow-up.
|
|
708
991
|
def order_stat (op, args, kw)
|
|
709
992
|
has_group, fused = AxisGroup.parse_axis(kw[:axis])
|
|
710
|
-
return value.public_send(op, *args) unless has_group
|
|
993
|
+
return value.public_send(op, *args, **kw) unless has_group
|
|
711
994
|
unless fused.empty?
|
|
712
995
|
raise NotImplementedError,
|
|
713
996
|
"axis_group: folding a band into an order statistic (axis: [:group, k]) " \
|
|
714
997
|
"is not supported; order statistics gather every member of a group"
|
|
715
998
|
end
|
|
716
|
-
|
|
999
|
+
check_reduce_keywords(op, kw)
|
|
1000
|
+
res = composite_order(op, args)
|
|
1001
|
+
if op == :quantile
|
|
1002
|
+
res.map { |r| finish_reduction(op, kw, r) }
|
|
1003
|
+
else
|
|
1004
|
+
finish_reduction(op, kw, res)
|
|
1005
|
+
end
|
|
717
1006
|
end
|
|
718
1007
|
|
|
719
1008
|
# Per-group order statistic over the composite grouping. A flat grouping (no
|
|
@@ -727,20 +1016,20 @@ class CAGroupIterator
|
|
|
727
1016
|
def composite_order (op, args)
|
|
728
1017
|
ccat, kdims, gslots, bslots, gaxes = composite_layout
|
|
729
1018
|
nout = (op == :quantile) ? 5 : 1
|
|
730
|
-
if bslots
|
|
1019
|
+
if whole_block?(bslots)
|
|
731
1020
|
res = value.group_by_category(ccat).public_send(op, *args)
|
|
732
1021
|
return nout == 1 ? res.reshape(*kdims) : res.map { |r| r.reshape(*kdims) }
|
|
733
1022
|
end
|
|
734
1023
|
out_shape = spec.slot_meta.map { |m| m[:kind] == :group ? m[:k] : m[:len] }
|
|
735
|
-
outs =
|
|
1024
|
+
outs = nil
|
|
736
1025
|
each_band_block(ccat, gslots, bslots, gaxes) do |_vi, out_idx, _co, gi|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
end
|
|
1026
|
+
results = gi.public_send(op, *args)
|
|
1027
|
+
results = [results] unless results.is_a?(Array) # quantile answers five
|
|
1028
|
+
# each output takes the kind the categorical answered in
|
|
1029
|
+
outs ||= results.map { |r| blank_of(r, out_shape) }
|
|
1030
|
+
results.each_with_index { |r, q| outs[q][*out_idx] = r.reshape(*kdims) }
|
|
743
1031
|
end
|
|
1032
|
+
outs ||= Array.new(nout) { blank_of(value, out_shape) }
|
|
744
1033
|
nout == 1 ? outs[0] : outs
|
|
745
1034
|
end
|
|
746
1035
|
|
|
@@ -782,7 +1071,15 @@ class CAGroupIterator
|
|
|
782
1071
|
end
|
|
783
1072
|
out
|
|
784
1073
|
end
|
|
1074
|
+
|
|
1075
|
+
# lag and strip_mask(method:) give the source's values moved or held in
|
|
1076
|
+
# place, so the result shows the source's attributes, as CArray#lag and
|
|
1077
|
+
# CArray#strip_mask(method:) do.
|
|
1078
|
+
prepend(Module.new do
|
|
1079
|
+
def lag (*args, **keywords) = value.__send__(:carry_attrs, super)
|
|
1080
|
+
def strip_mask (**keywords) = value.__send__(:carry_attrs, super)
|
|
1081
|
+
end)
|
|
785
1082
|
end
|
|
786
1083
|
|
|
787
1084
|
require "carray/categorical"
|
|
788
|
-
CArray.
|
|
1085
|
+
CArray.send(:register_axis_group_classes, CACategorical, AxisGroup)
|