carray 3.0.0 → 3.0.1
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/.yardopts +2 -1
- data/CHANGELOG.md +325 -3
- data/{NEWS.md → CHANGELOG.v1.md} +1 -1
- data/README.md +32 -32
- data/carray.gemspec +12 -8
- data/ext/ca_axis_group.c +55 -19
- data/ext/ca_binop_dispatch.c +5 -6
- data/ext/ca_binop_dispatch.h +0 -7
- data/ext/ca_categorical_iterator.c +13 -13
- data/ext/ca_for_each_element.h +6 -8
- data/ext/ca_group_iter.c +1 -1
- data/ext/ca_kernel_iterator.c +69 -23
- data/ext/ca_kernel_iterator.h +3 -10
- data/ext/ca_obj_bincmp.c +57 -27
- data/ext/ca_obj_binop.c +86 -30
- data/ext/ca_obj_const_string.c +3 -1
- data/ext/ca_obj_face.c +80 -0
- data/ext/ca_obj_face.h +33 -1
- data/ext/ca_obj_fake.c +11 -0
- data/ext/ca_obj_fixlen_string.c +3 -1
- data/ext/ca_obj_grid.c +11 -3
- data/ext/ca_obj_meld.c +13 -8
- data/ext/ca_obj_moncmp.c +33 -14
- data/ext/ca_obj_monop.c +19 -7
- data/ext/ca_obj_object.c +99 -4
- data/ext/ca_obj_record.c +3 -1
- data/ext/ca_obj_refer.c +30 -6
- data/ext/ca_obj_roll.c +13 -5
- data/ext/ca_obj_select_axis.c +16 -6
- data/ext/ca_obj_shift.c +3 -3
- data/ext/ca_obj_stride.c +106 -23
- data/ext/ca_obj_string.c +3 -1
- data/ext/ca_obj_tile.c +13 -5
- data/ext/ca_obj_time.c +3 -1
- data/ext/ca_obj_timedelta.c +3 -1
- data/ext/ca_obj_transpose.c +1 -1
- data/ext/ca_obj_triop.c +62 -23
- data/ext/ca_obj_window.c +86 -17
- data/ext/ca_op_cmplx64.h +123 -0
- data/ext/ca_op_ipower.c +0 -3
- data/ext/ca_sort_kernels.h +5 -5
- data/ext/ca_sweep_engine.c +78 -35
- data/ext/ca_sweep_engine.h +13 -4
- data/ext/ca_transform_common.c +7 -0
- data/ext/carray.h +127 -130
- data/ext/carray_access.c +56 -34
- data/ext/carray_bincount.c +8 -8
- data/ext/carray_broadcast.c +99 -6
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +1491 -0
- data/ext/carray_call_cfunc.h +153 -0
- data/ext/carray_cast.c +69 -31
- data/ext/carray_conversion.c +22 -22
- data/ext/carray_copy.c +12 -2
- data/ext/carray_core.c +57 -10
- data/ext/carray_count.c +1 -1
- data/ext/carray_element.c +7 -7
- data/ext/carray_factorize.c +28 -28
- data/ext/carray_hold.c +1 -1
- data/ext/carray_index_classifier.c +7 -21
- data/ext/carray_internal.h +19 -3
- data/ext/carray_kernels_bincmp.c +1 -0
- data/ext/carray_kernels_binop.c +320 -298
- data/ext/carray_kernels_init.c +1095 -0
- data/ext/carray_kernels_map.c +1 -0
- data/ext/carray_kernels_moncmp.c +1 -0
- data/ext/carray_kernels_monop.c +412 -411
- data/ext/carray_kernels_reduce_aggregate.c +88 -87
- data/ext/carray_kernels_reduce_boolean.c +1 -0
- data/ext/carray_kernels_reduce_cumulative.c +54 -53
- data/ext/carray_kernels_reduce_extreme.c +51 -50
- data/ext/carray_kernels_reduce_variance.c +1 -0
- data/ext/carray_kernels_scan.c +1 -0
- data/ext/carray_kernels_search.c +93 -92
- data/ext/carray_kernels_sort.c +1 -0
- data/ext/carray_kernels_triop.c +1 -0
- data/ext/carray_lazy.c +175 -5
- data/ext/carray_mask.c +6 -5
- data/ext/carray_median_percentile.c +1 -1
- data/ext/carray_memory_view.c +46 -21
- data/ext/carray_operator.c +46 -49
- data/ext/carray_partition.c +4 -4
- data/ext/carray_random.c +7 -7
- data/ext/carray_scatter.c +1 -1
- data/ext/carray_sort.c +3 -3
- data/ext/carray_sort_kernel.c +10 -10
- data/ext/carray_test.c +1 -1
- data/ext/extconf.rb +18 -0
- data/ext/mk_call_cfunc.rb +243 -52
- data/ext/mkkernel.rb +354 -100
- data/ext/ruby_carray.c +18 -16
- data/ext/version.h +4 -4
- data/lib/carray/autoload_carray.rb +19 -0
- data/lib/carray/autoload_method_extension.rb +2 -1
- data/lib/carray/axis_group.rb +7 -7
- data/lib/carray/basics.rb +59 -59
- data/lib/carray/bincount_nd.rb +12 -12
- data/lib/carray/block_iterator.rb +24 -14
- data/lib/carray/categorical.rb +4 -4
- data/lib/carray/categorical_iterator.rb +98 -76
- data/lib/carray/conditional.rb +14 -14
- data/lib/carray/construct.rb +21 -0
- data/lib/carray/core_extensions.rb +62 -5
- data/lib/carray/data_type_extension.rb +18 -1
- data/lib/carray/frame/concat.rb +3 -3
- data/lib/carray/frame/convert.rb +1 -1
- data/lib/carray/frame/frame.rb +4 -11
- data/lib/carray/frame/io.rb +159 -2
- data/lib/carray/frame/sort.rb +1 -1
- data/lib/carray/frame/verbs.rb +18 -1
- data/lib/carray/fuse_source.rb +123 -0
- data/lib/carray/fusion.rb +218 -0
- data/lib/carray/histogram.rb +16 -16
- data/lib/carray/inspect.rb +1 -7
- data/lib/carray/iterator.rb +4 -3
- data/lib/carray/lazy.rb +125 -73
- data/lib/carray/meld_reduce.rb +2 -2
- data/lib/carray/methods/align_addr.rb +1 -1
- data/lib/carray/methods/composition.rb +1 -1
- data/lib/carray/methods/is_in.rb +12 -12
- data/lib/carray/methods/locate_addr.rb +6 -1
- data/lib/carray/methods/mask_duplicates.rb +1 -1
- data/lib/carray/methods/meshgrid.rb +4 -5
- data/lib/carray/methods/mode.rb +2 -2
- data/lib/carray/methods/nunique.rb +1 -1
- data/lib/carray/methods/snap.rb +7 -2
- data/lib/carray/methods/unique.rb +3 -3
- data/lib/carray/methods/value_counts.rb +2 -2
- data/lib/carray/runtime.rb +0 -19
- data/lib/carray/slab_iterator.rb +20 -7
- data/lib/carray/string_operation_extension.rb +5 -5
- data/lib/carray/time.rb +890 -491
- data/lib/carray/window_iterator.rb +285 -13
- data/lib/carray.rb +5 -5
- data/yard-stubs/ca_obj_array.rb +385 -0
- data/yard-stubs/ca_obj_bitarray.rb +38 -0
- data/yard-stubs/ca_obj_bitfield.rb +43 -0
- data/yard-stubs/ca_obj_block.rb +73 -0
- data/yard-stubs/ca_obj_byte_swap.rb +56 -0
- data/yard-stubs/ca_obj_fake.rb +31 -0
- data/yard-stubs/ca_obj_farray.rb +32 -0
- data/yard-stubs/ca_obj_field.rb +45 -0
- data/yard-stubs/ca_obj_grid.rb +35 -0
- data/yard-stubs/ca_obj_refer.rb +72 -0
- data/yard-stubs/ca_obj_roll.rb +45 -0
- data/yard-stubs/ca_obj_shift.rb +43 -0
- data/yard-stubs/ca_obj_stride.rb +181 -0
- data/yard-stubs/ca_obj_tile.rb +29 -0
- data/yard-stubs/ca_obj_transpose.rb +40 -0
- data/yard-stubs/ca_obj_window.rb +49 -0
- data/yard-stubs/carray_access.rb +131 -0
- data/yard-stubs/carray_attribute.rb +246 -0
- data/yard-stubs/carray_broadcast.rb +37 -0
- data/yard-stubs/carray_cast.rb +489 -0
- data/yard-stubs/carray_class.rb +65 -0
- data/yard-stubs/carray_conversion.rb +76 -0
- data/yard-stubs/carray_copy.rb +79 -0
- data/yard-stubs/carray_core.rb +114 -0
- data/yard-stubs/carray_count.rb +79 -0
- data/yard-stubs/carray_element.rb +108 -0
- data/yard-stubs/carray_generate.rb +66 -0
- data/yard-stubs/carray_lazy.rb +23 -0
- data/yard-stubs/carray_loop.rb +140 -0
- data/yard-stubs/carray_mask.rb +259 -0
- data/yard-stubs/carray_math.rb +132 -0
- data/yard-stubs/carray_mathfunc.rb +45 -0
- data/yard-stubs/carray_median_percentile.rb +89 -0
- data/yard-stubs/carray_memory_view.rb +163 -0
- data/yard-stubs/carray_order.rb +312 -0
- data/yard-stubs/carray_random.rb +89 -0
- data/yard-stubs/carray_scatter.rb +106 -0
- data/yard-stubs/carray_slab.rb +57 -0
- data/yard-stubs/carray_sort.rb +163 -0
- data/yard-stubs/carray_test.rb +85 -0
- data/yard-stubs/carray_undef.rb +64 -0
- data/yard-stubs/carray_utils.rb +97 -0
- data/yard-stubs/ruby_carray.rb +193 -0
- metadata +59 -10
- data/ext/ca_obj_unbound_repeat.c +0 -496
|
@@ -26,18 +26,18 @@
|
|
|
26
26
|
# the mask contract (empty / all-masked -> identity for sum/prod, UNDEF for
|
|
27
27
|
# ratios) carries through unchanged. Results are length-k CArrays aligned to
|
|
28
28
|
# `cat.labels`; undefined slots are MASKED cells (never magic floats). Output
|
|
29
|
-
#
|
|
29
|
+
# data type and the empty / all-masked answer per method:
|
|
30
30
|
#
|
|
31
31
|
# elements -> int64, classified cells (incl. masked)
|
|
32
32
|
# count / count_not_masked / count_masked / count(v) -> int64
|
|
33
|
-
# sum -> value
|
|
33
|
+
# sum -> value data type, empty/all-masked = 0 (identity)
|
|
34
34
|
# prod -> float64, empty/all-masked = 1 (identity)
|
|
35
|
-
# max / min -> value
|
|
35
|
+
# max / min -> value data type, empty/all-masked = MASKED
|
|
36
36
|
# mean -> float64, empty/all-masked = MASKED
|
|
37
37
|
# median / percentile -> float64, empty/all-masked = MASKED
|
|
38
38
|
# variance / stddev (sample, ddof=1) -> float64, empty/all-masked = MASKED,
|
|
39
39
|
# single value = 0.0 (n=1 contract)
|
|
40
|
-
# all / any -> boolean (boolean value
|
|
40
|
+
# all / any -> boolean (boolean value data type only)
|
|
41
41
|
# labels -> cat.labels
|
|
42
42
|
#
|
|
43
43
|
# Generic iteration (the escape hatch for statistics not in the named surface),
|
|
@@ -125,7 +125,7 @@ class CACategoricalIterator < CAIterator
|
|
|
125
125
|
# 1-D value there is no fiber structure to broadcast into, so a mismatch
|
|
126
126
|
# is unrecoverable (preserves the old strict check). For higher-rank
|
|
127
127
|
# value, defer validation to reduce time — check only that cat.ndim fits
|
|
128
|
-
# one of the 3 axis: cases
|
|
128
|
+
# one of the 3 axis: cases;
|
|
129
129
|
# any no-axis reduce called on this iterator will surface the mismatch
|
|
130
130
|
# because @grouped stays undefined.
|
|
131
131
|
if value.ndim == 1 ||
|
|
@@ -217,21 +217,20 @@ class CACategoricalIterator < CAIterator
|
|
|
217
217
|
# @return [CArray] length-k int64, aligned to {#labels}
|
|
218
218
|
# @overload count(axis:)
|
|
219
219
|
# No-arg + axis: = per-fiber per-category count_not_masked (shape [K, ...band]).
|
|
220
|
-
# `count(v, axis:)` (value equality) and `count(UNDEF, axis:)` are
|
|
221
|
-
#
|
|
220
|
+
# `count(v, axis:)` (value equality) and `count(UNDEF, axis:)` are not
|
|
221
|
+
# implemented; use them without `axis:`.
|
|
222
222
|
# @param axis [Integer]
|
|
223
223
|
# @return [CArray]
|
|
224
224
|
def count (*args, axis: nil)
|
|
225
225
|
if axis
|
|
226
226
|
return count_not_masked(axis: axis) if args.empty?
|
|
227
227
|
raise NotImplementedError,
|
|
228
|
-
"CACategoricalIterator#count(v, axis:) not
|
|
229
|
-
"value-equality count
|
|
230
|
-
"PROPOSAL_CATEGORICAL_REDUCE_AXIS."
|
|
228
|
+
"CACategoricalIterator#count(v, axis:) is not implemented — " \
|
|
229
|
+
"value-equality count is available without axis:."
|
|
231
230
|
end
|
|
232
231
|
return count_not_masked if args.empty?
|
|
233
232
|
# Delegate per group to CArray#count (handles count(UNDEF) -> masked count and
|
|
234
|
-
# count(v) alike, with core's exact
|
|
233
|
+
# count(v) alike, with core's exact data type equality). The group slice is a
|
|
235
234
|
# CABlock, whose own #count is the block geometry accessor, so dispatch
|
|
236
235
|
# CArray#count explicitly. (Not fused: a value-equality reduceat would have
|
|
237
236
|
# to reproduce core's cross-type / out-of-range equality exactly.)
|
|
@@ -244,70 +243,84 @@ class CACategoricalIterator < CAIterator
|
|
|
244
243
|
# Empty categories are `0`.
|
|
245
244
|
# @return [CArray]
|
|
246
245
|
# @overload count_masked(axis:)
|
|
247
|
-
#
|
|
248
|
-
# "assigned count" scatter that counts cells regardless of value mask).
|
|
246
|
+
# Not implemented; call it without `axis:`.
|
|
249
247
|
# @param axis [Integer]
|
|
250
248
|
# @return [CArray]
|
|
251
249
|
def count_masked(axis: nil)
|
|
252
250
|
if axis
|
|
253
251
|
raise NotImplementedError,
|
|
254
|
-
"CACategoricalIterator#count_masked(axis:) not
|
|
255
|
-
"
|
|
256
|
-
"(needs a separate value-mask-only scatter kernel)."
|
|
252
|
+
"CACategoricalIterator#count_masked(axis:) is not implemented — " \
|
|
253
|
+
"call it without axis:."
|
|
257
254
|
end
|
|
258
255
|
m = moments
|
|
259
256
|
m ? @elements - m[:count] : per_category(CA_INT64) { |s| s.count_masked }
|
|
260
257
|
end
|
|
261
258
|
|
|
262
259
|
# @overload sum
|
|
263
|
-
# Returns per-category sums in the
|
|
264
|
-
#
|
|
265
|
-
#
|
|
266
|
-
#
|
|
260
|
+
# Returns per-category sums in the data type `CArray#sum` promotes the value
|
|
261
|
+
# to (float64 for an integer value). `accumulate` is the same fold kept in
|
|
262
|
+
# the value's own type. An empty or fully-masked category sums the empty
|
|
263
|
+
# set, which is the additive identity `0` (unmasked) — the same contract as
|
|
264
|
+
# `CArray#sum` on an empty / all-masked array.
|
|
267
265
|
# @return [CArray]
|
|
268
266
|
# @overload sum(axis:)
|
|
269
267
|
# Returns per-category sums per fiber along `axis`. Cat may be 1-D (case
|
|
270
268
|
# A, broadcasts across band axes), same rank as source (case B, per-fiber
|
|
271
269
|
# independent classifier), or one rank less (band-only, constant along
|
|
272
|
-
# reduce axis). Output shape = `[K, ...source.shape without axis]`.
|
|
273
|
-
# PROPOSAL_CATEGORICAL_REDUCE_AXIS.
|
|
270
|
+
# reduce axis). Output shape = `[K, ...source.shape without axis]`.
|
|
274
271
|
# @param axis [Integer] reduce axis of the source value.
|
|
275
272
|
# @return [CArray]
|
|
276
273
|
def sum(axis: nil)
|
|
277
274
|
return axis_sum(axis) if axis
|
|
278
275
|
m = moments
|
|
279
|
-
return per_category(
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
276
|
+
return per_category(core_reduce_type(:sum)) { |s| s.sum } unless m
|
|
277
|
+
m[:sum].copy # the moments sum IS the core fold (empty -> 0.0)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# @overload accumulate
|
|
281
|
+
# Returns per-category sums folded in the value's own data type, wrapping at
|
|
282
|
+
# its width, as the core `accumulate` does. This is the exact in-type fold:
|
|
283
|
+
# `sum` reads its answer off a float64 moment and casts back, so it loses
|
|
284
|
+
# the low bits of a wide integer payload and does not wrap. An empty or
|
|
285
|
+
# fully-masked category accumulates the empty set, the additive identity `0`
|
|
286
|
+
# (unmasked).
|
|
287
|
+
# @return [CArray]
|
|
288
|
+
# @overload accumulate(axis:)
|
|
289
|
+
# Per-fiber per-category in-type sums along `axis`. Output shape =
|
|
290
|
+
# `[K, ...source.shape without axis]`.
|
|
291
|
+
# @param axis [Integer] reduce axis of the source value.
|
|
292
|
+
# @return [CArray]
|
|
293
|
+
def accumulate(axis: nil)
|
|
294
|
+
return axis_by_masked_copy(axis, :accumulate, core_reduce_type(:accumulate)) if axis
|
|
295
|
+
per_category(core_reduce_type(:accumulate)) { |s| s.accumulate }
|
|
283
296
|
end
|
|
284
297
|
|
|
285
298
|
# @overload max
|
|
286
|
-
# Returns per-category maxima in the value
|
|
299
|
+
# Returns per-category maxima in the value data type. Empty categories are
|
|
287
300
|
# MASKED.
|
|
288
301
|
# @return [CArray]
|
|
289
302
|
# @overload max(axis:)
|
|
290
|
-
# Per-fiber per-category maxima along `axis` (h
|
|
303
|
+
# Per-fiber per-category maxima along `axis` (h's data type, masked where empty).
|
|
291
304
|
# @param axis [Integer]
|
|
292
305
|
# @return [CArray]
|
|
293
306
|
def max(axis: nil)
|
|
294
307
|
return axis_moments(axis)[:max] if axis
|
|
295
308
|
m = moments
|
|
296
|
-
m ? m[:max] : per_category(
|
|
309
|
+
m ? m[:max] : per_category(core_reduce_type(:max)) { |s| s.max }
|
|
297
310
|
end
|
|
298
311
|
|
|
299
312
|
# @overload min
|
|
300
|
-
# Returns per-category minima in the value
|
|
313
|
+
# Returns per-category minima in the value data type. Empty categories are
|
|
301
314
|
# MASKED.
|
|
302
315
|
# @return [CArray]
|
|
303
316
|
# @overload min(axis:)
|
|
304
|
-
# Per-fiber per-category minima along `axis` (h
|
|
317
|
+
# Per-fiber per-category minima along `axis` (h's data type, masked where empty).
|
|
305
318
|
# @param axis [Integer]
|
|
306
319
|
# @return [CArray]
|
|
307
320
|
def min(axis: nil)
|
|
308
321
|
return axis_moments(axis)[:min] if axis
|
|
309
322
|
m = moments
|
|
310
|
-
m ? m[:min] : per_category(
|
|
323
|
+
m ? m[:min] : per_category(core_reduce_type(:min)) { |s| s.min }
|
|
311
324
|
end
|
|
312
325
|
|
|
313
326
|
# @overload mean
|
|
@@ -320,7 +333,7 @@ class CACategoricalIterator < CAIterator
|
|
|
320
333
|
def mean(axis: nil)
|
|
321
334
|
return axis_mean(axis) if axis
|
|
322
335
|
m = moments
|
|
323
|
-
return per_category(
|
|
336
|
+
return per_category(core_reduce_type(:mean)) { |s| s.mean } unless m
|
|
324
337
|
cnt = m[:count]
|
|
325
338
|
out = m[:sum] / cnt.float64 # count 0 -> NaN, masked next
|
|
326
339
|
out[cnt.eq(0)] = UNDEF # empty / all-masked category -> MASKED
|
|
@@ -345,7 +358,7 @@ class CACategoricalIterator < CAIterator
|
|
|
345
358
|
def percentile (p, axis: nil)
|
|
346
359
|
axis_order_stat_defer!(:percentile) if axis
|
|
347
360
|
unless MONOID_TYPES.include?(@grouped.data_type)
|
|
348
|
-
return per_category(
|
|
361
|
+
return per_category(core_reduce_type(:percentile, p)) { |s| s.percentile(p) }
|
|
349
362
|
end
|
|
350
363
|
out = CArray.float64(@k)
|
|
351
364
|
@grouped.send(:__reduceat_percentile__, @offsets, p.to_f, out)
|
|
@@ -374,9 +387,9 @@ class CACategoricalIterator < CAIterator
|
|
|
374
387
|
# sample variance.
|
|
375
388
|
# @return [CArray]
|
|
376
389
|
def variance(axis: nil)
|
|
377
|
-
return
|
|
390
|
+
return axis_by_masked_copy(axis, :variance) if axis
|
|
378
391
|
m = moments
|
|
379
|
-
return per_category(
|
|
392
|
+
return per_category(core_reduce_type(:variance)) { |s| s.variance } unless m
|
|
380
393
|
cnt = m[:count]
|
|
381
394
|
means = m[:sum] / cnt.float64 # per-segment mean (garbage where count 0/1,
|
|
382
395
|
out = CArray.float64(@k) # ignored by the kernel's n<2 guards)
|
|
@@ -390,9 +403,9 @@ class CACategoricalIterator < CAIterator
|
|
|
390
403
|
# single-value `0.0`).
|
|
391
404
|
# @return [CArray]
|
|
392
405
|
def stddev(axis: nil)
|
|
393
|
-
return
|
|
406
|
+
return axis_by_masked_copy(axis, :stddev) if axis
|
|
394
407
|
m = moments
|
|
395
|
-
return per_category(
|
|
408
|
+
return per_category(core_reduce_type(:stddev)) { |s| s.stddev } unless m
|
|
396
409
|
variance.sqrt # sqrt propagates the n=0 mask
|
|
397
410
|
end
|
|
398
411
|
|
|
@@ -408,7 +421,7 @@ class CACategoricalIterator < CAIterator
|
|
|
408
421
|
# @return [CArray]
|
|
409
422
|
def prod(axis: nil)
|
|
410
423
|
return axis_prod(axis) if axis
|
|
411
|
-
return per_category(
|
|
424
|
+
return per_category(core_reduce_type(:prod)) { |s| s.prod } unless MONOID_TYPES.include?(@grouped.data_type)
|
|
412
425
|
out = CArray.float64(@k)
|
|
413
426
|
@grouped.send(:__reduceat_prod__, @offsets, out)
|
|
414
427
|
out
|
|
@@ -417,7 +430,7 @@ class CACategoricalIterator < CAIterator
|
|
|
417
430
|
# @overload all
|
|
418
431
|
# Returns the per-category `all` as boolean (matching `CArray#all`): true
|
|
419
432
|
# iff every present value is truthy (empty category -> true, vacuously).
|
|
420
|
-
# The value
|
|
433
|
+
# The value data type must be boolean, as for `CArray#all`.
|
|
421
434
|
# @return [CArray]
|
|
422
435
|
def all
|
|
423
436
|
aa = all_any
|
|
@@ -427,7 +440,7 @@ class CACategoricalIterator < CAIterator
|
|
|
427
440
|
# @overload any
|
|
428
441
|
# Returns the per-category `any` as boolean (matching `CArray#any`): true
|
|
429
442
|
# iff some present value is truthy (empty category -> false). The value
|
|
430
|
-
#
|
|
443
|
+
# data type must be boolean, as for `CArray#any`.
|
|
431
444
|
# @return [CArray]
|
|
432
445
|
def any
|
|
433
446
|
aa = all_any
|
|
@@ -438,11 +451,11 @@ class CACategoricalIterator < CAIterator
|
|
|
438
451
|
|
|
439
452
|
# @overload minmax
|
|
440
453
|
# Returns the per-category `[min, max]` pair (each a length-k CArray in the
|
|
441
|
-
# value
|
|
454
|
+
# value data type; empty categories MASKED), matching `CArray#minmax`. Both come
|
|
442
455
|
# from the single cached moments pass.
|
|
443
456
|
# @return [Array<CArray>]
|
|
444
457
|
# @overload minmax(axis:)
|
|
445
|
-
# Per-fiber `[min_ca, max_ca]` along `axis` (each shape [K, ...band], h
|
|
458
|
+
# Per-fiber `[min_ca, max_ca]` along `axis` (each shape [K, ...band], h's data type,
|
|
446
459
|
# empty group cells MASKED). Ruby Array of two CArrays, not stacked.
|
|
447
460
|
# @param axis [Integer]
|
|
448
461
|
# @return [Array<CArray>]
|
|
@@ -458,9 +471,9 @@ class CACategoricalIterator < CAIterator
|
|
|
458
471
|
# reuses the centred two-pass kernel with no extra walk.
|
|
459
472
|
# @return [CArray]
|
|
460
473
|
def variancep(axis: nil)
|
|
461
|
-
return
|
|
474
|
+
return axis_by_masked_copy(axis, :variancep) if axis
|
|
462
475
|
m = moments
|
|
463
|
-
return per_category(
|
|
476
|
+
return per_category(core_reduce_type(:variancep)) { |s| s.variancep } unless m
|
|
464
477
|
cnt = m[:count]
|
|
465
478
|
vp = variance * (cnt - 1).float64 / cnt.float64
|
|
466
479
|
vp[cnt.eq(0)] = UNDEF # empty / all-masked stays masked
|
|
@@ -475,9 +488,9 @@ class CACategoricalIterator < CAIterator
|
|
|
475
488
|
# @param axis [Integer]
|
|
476
489
|
# @return [CArray]
|
|
477
490
|
def stddevp(axis: nil)
|
|
478
|
-
return
|
|
491
|
+
return axis_by_masked_copy(axis, :stddevp) if axis
|
|
479
492
|
m = moments
|
|
480
|
-
return per_category(
|
|
493
|
+
return per_category(core_reduce_type(:stddevp)) { |s| s.stddevp } unless m
|
|
481
494
|
variancep.sqrt
|
|
482
495
|
end
|
|
483
496
|
|
|
@@ -660,8 +673,8 @@ class CACategoricalIterator < CAIterator
|
|
|
660
673
|
# Excluded (out-of-vocabulary / masked-code) and source-masked cells join no
|
|
661
674
|
# running total and are UNDEF. Mirroring the reductions (sum / mean), a scan
|
|
662
675
|
# takes no axis argument. cumsum / cumprod -> float64, cummax / cummin
|
|
663
|
-
# preserve the value
|
|
664
|
-
# ordinal); an object value
|
|
676
|
+
# preserve the value data type, cumcount -> int64 (1-based within-category
|
|
677
|
+
# ordinal); an object value data type is carried by the kernel's object branch.
|
|
665
678
|
|
|
666
679
|
# @overload cumsum
|
|
667
680
|
# Per-category inclusive running sum (float64), source-shaped.
|
|
@@ -670,10 +683,10 @@ class CACategoricalIterator < CAIterator
|
|
|
670
683
|
# Per-category inclusive running product (float64), source-shaped.
|
|
671
684
|
# @return [CArray]
|
|
672
685
|
# @overload cummax
|
|
673
|
-
# Per-category inclusive running maximum (value
|
|
686
|
+
# Per-category inclusive running maximum (value data type), source-shaped.
|
|
674
687
|
# @return [CArray]
|
|
675
688
|
# @overload cummin
|
|
676
|
-
# Per-category inclusive running minimum (value
|
|
689
|
+
# Per-category inclusive running minimum (value data type), source-shaped.
|
|
677
690
|
# @return [CArray]
|
|
678
691
|
# @overload cumcount
|
|
679
692
|
# Per-category 1-based within-category ordinal (int64), source-shaped.
|
|
@@ -688,7 +701,7 @@ class CACategoricalIterator < CAIterator
|
|
|
688
701
|
# the fused per-fiber scatter-reduce C kernel and cached (matches the flat
|
|
689
702
|
# #moments caching in spirit: pay one kernel per {iterator, axis} pair, share
|
|
690
703
|
# across sum / mean / min / max / minmax / count* consumers). Returns
|
|
691
|
-
# `{count: <int64>, sum: <float64>, min: <h
|
|
704
|
+
# `{count: <int64>, sum: <float64>, min: <h's type, masked>, max: <h's type, masked>}`,
|
|
692
705
|
# all shape [K, ...band].
|
|
693
706
|
def axis_moments (axis)
|
|
694
707
|
@axis_moments_cache ||= {}
|
|
@@ -712,13 +725,10 @@ class CACategoricalIterator < CAIterator
|
|
|
712
725
|
@axis_moments_cache[axis] = {count: counts, sum: sums, min: mins, max: maxs}
|
|
713
726
|
end
|
|
714
727
|
|
|
715
|
-
# Axis-aware sum:
|
|
716
|
-
#
|
|
728
|
+
# Axis-aware sum: the moments sum is already the core fold in the core's own
|
|
729
|
+
# type, so it is handed back as is (an empty group cell carries identity 0.0).
|
|
717
730
|
def axis_sum (axis)
|
|
718
|
-
|
|
719
|
-
out = CArray.new(@value.data_type, m[:sum].shape)
|
|
720
|
-
out[] = m[:sum]
|
|
721
|
-
out
|
|
731
|
+
axis_moments(axis)[:sum].copy
|
|
722
732
|
end
|
|
723
733
|
|
|
724
734
|
# Axis-aware mean: sums / counts (float64); empty group cells (count=0) MASKED.
|
|
@@ -731,17 +741,19 @@ class CACategoricalIterator < CAIterator
|
|
|
731
741
|
out
|
|
732
742
|
end
|
|
733
743
|
|
|
734
|
-
# Axis-aware
|
|
735
|
-
#
|
|
736
|
-
#
|
|
737
|
-
#
|
|
738
|
-
#
|
|
739
|
-
#
|
|
744
|
+
# Axis-aware reduction by masked copy — Ruby-level per-c mask, then delegate
|
|
745
|
+
# to the source's own axis-aware kernel, so the core contract for `op` rides
|
|
746
|
+
# unchanged. Used by the variance family (a centred two-pass numeric
|
|
747
|
+
# aggregate, hitting the same ε-close kernel per (group, axis) that
|
|
748
|
+
# CArray#variance uses) and by `accumulate` (whose in-type wrapping fold has
|
|
749
|
+
# no float64 moment to read it off). Order (median / percentile / quantile)
|
|
750
|
+
# is genuinely order-statistical (needs a sort per group) and remains
|
|
751
|
+
# deferred.
|
|
740
752
|
#
|
|
741
753
|
# Cost: K axis-reductions over an h-shaped local (most cells masked away for
|
|
742
754
|
# each c) — bounded by K, typically small. A fused per-fiber variance
|
|
743
755
|
# kernel is a natural follow-on if bench demands it.
|
|
744
|
-
def
|
|
756
|
+
def axis_by_masked_copy (axis, op, out_data_type = CA_FLOAT64)
|
|
745
757
|
h = @value
|
|
746
758
|
unless axis.is_a?(Integer) && axis >= 0 && axis < h.ndim
|
|
747
759
|
raise ArgumentError,
|
|
@@ -750,7 +762,7 @@ class CACategoricalIterator < CAIterator
|
|
|
750
762
|
end
|
|
751
763
|
full_c = resolve_axis_codes(@cat.codes, h.shape, axis)
|
|
752
764
|
band = h.shape.dup; band.delete_at(axis)
|
|
753
|
-
out = CArray.
|
|
765
|
+
out = CArray.new(out_data_type, [@k] + band)
|
|
754
766
|
slot_idx = [nil] + [nil] * band.size # placeholder; c fills slot 0
|
|
755
767
|
codes_bad = full_c.has_mask? ? full_c.is_masked : nil
|
|
756
768
|
@k.times do |c|
|
|
@@ -855,9 +867,8 @@ class CACategoricalIterator < CAIterator
|
|
|
855
867
|
# Called from median / percentile / variance / stddev when axis: is given.
|
|
856
868
|
def axis_order_stat_defer! (op)
|
|
857
869
|
raise NotImplementedError,
|
|
858
|
-
"CACategoricalIterator##{op}(axis:) not
|
|
859
|
-
"statistics
|
|
860
|
-
"(needs per-fiber counting-sort C kernel, tracked separately)."
|
|
870
|
+
"CACategoricalIterator##{op}(axis:) is not implemented — order " \
|
|
871
|
+
"statistics are available without axis:."
|
|
861
872
|
end
|
|
862
873
|
|
|
863
874
|
# Drive a segment scan through the axis-group scan kernel: the whole value as
|
|
@@ -910,7 +921,7 @@ class CACategoricalIterator < CAIterator
|
|
|
910
921
|
|
|
911
922
|
# Fused per-segment weighted sum + weighted mean (one C pass over the grouped
|
|
912
923
|
# copy, weights in group order). Returns [wsum, wmean]; wmean is masked where a
|
|
913
|
-
# segment has no present (value AND weight) pair. Numeric value
|
|
924
|
+
# segment has no present (value AND weight) pair. Numeric value data types only.
|
|
914
925
|
def kernel_weighted (wg)
|
|
915
926
|
ws = CArray.float64(@k)
|
|
916
927
|
wm = CArray.float64(@k)
|
|
@@ -918,7 +929,7 @@ class CACategoricalIterator < CAIterator
|
|
|
918
929
|
[ws, wm]
|
|
919
930
|
end
|
|
920
931
|
|
|
921
|
-
# Per-group weighted fallback for non-numeric value
|
|
932
|
+
# Per-group weighted fallback for non-numeric value data types (complex): delegate
|
|
922
933
|
# each group to CArray#wsum / #wmean. Empty segments take the given identity.
|
|
923
934
|
def fold_weighted (wg, empty)
|
|
924
935
|
out = CArray.float64(@k)
|
|
@@ -943,9 +954,9 @@ class CACategoricalIterator < CAIterator
|
|
|
943
954
|
# Single-pass reduceat moments (count / sum / min / max per category), computed
|
|
944
955
|
# once over the grouped copy and cached — the whole point of the eager copy is
|
|
945
956
|
# that one scatter is followed by cheap single-pass reductions with no
|
|
946
|
-
# per-segment views. Nil for a non-numeric value
|
|
957
|
+
# per-segment views. Nil for a non-numeric value data type (complex / object /
|
|
947
958
|
# bool), where the monoid reductions fall back to per_category.
|
|
948
|
-
# numeric value
|
|
959
|
+
# numeric value data types the C moments kernel handles (int8..float64); bool /
|
|
949
960
|
# complex / object fall back to per_category.
|
|
950
961
|
MONOID_TYPES = %i[int8 uint8 int16 uint16 int32 uint32
|
|
951
962
|
int64 uint64 float32 float64].freeze
|
|
@@ -965,7 +976,7 @@ class CACategoricalIterator < CAIterator
|
|
|
965
976
|
end
|
|
966
977
|
|
|
967
978
|
# Single-pass fused group-local argmin / argmax (min_index / max_index),
|
|
968
|
-
# cached. Nil for a non-numeric value
|
|
979
|
+
# cached. Nil for a non-numeric value data type (fall back to per_category).
|
|
969
980
|
def arg_minmax
|
|
970
981
|
return @arg_minmax if defined?(@arg_minmax)
|
|
971
982
|
@arg_minmax =
|
|
@@ -978,7 +989,7 @@ class CACategoricalIterator < CAIterator
|
|
|
978
989
|
end
|
|
979
990
|
|
|
980
991
|
# Single-pass fused per-category boolean all / any, cached. Nil unless the
|
|
981
|
-
# value
|
|
992
|
+
# value data type is boolean (fall back to per_category, which raises like
|
|
982
993
|
# CArray#all on a non-boolean).
|
|
983
994
|
def all_any
|
|
984
995
|
return @all_any if defined?(@all_any)
|
|
@@ -993,11 +1004,22 @@ class CACategoricalIterator < CAIterator
|
|
|
993
1004
|
|
|
994
1005
|
# Build a length-k typed output by folding each category's members with the
|
|
995
1006
|
# given reduction block. Fallback path (order statistics, and monoids on a
|
|
996
|
-
# non-numeric value
|
|
1007
|
+
# non-numeric value data type): each group is delegated to the same CArray
|
|
997
1008
|
# reduction, so the per-group result matches `CArray#<reduction>` over that
|
|
998
1009
|
# group's members — the mask carries the "insufficient present data" contract
|
|
999
1010
|
# for free (an all-masked group reduces like an empty one; identity-bearing
|
|
1000
1011
|
# reductions return their identity, ratios return UNDEF; see ext ERI).
|
|
1012
|
+
# The data type the core reduction `op` promotes this value to. Asked of the
|
|
1013
|
+
# core itself -- a one-cell reduction of the value's type -- rather than
|
|
1014
|
+
# restated here, so a per-category answer cannot drift from `CArray#<op>`
|
|
1015
|
+
# (`sum` on an integer promotes, `accumulate` stays, `min` / `max` keep the
|
|
1016
|
+
# type but a boolean widens, `prod` on an object stays an object). A payload
|
|
1017
|
+
# the core refuses to fold this way raises here, with the core's own error.
|
|
1018
|
+
def core_reduce_type (op, *args)
|
|
1019
|
+
(@core_reduce_type ||= {})[[op, args]] ||=
|
|
1020
|
+
CArray.new(@grouped.data_type, [1, 1]).public_send(op, *args, axis: 1).data_type
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1001
1023
|
def per_category (data_type)
|
|
1002
1024
|
out = CArray.new(data_type, [@k])
|
|
1003
1025
|
@k.times { |c| out[c] = yield(group_slice(c)) }
|
data/lib/carray/conditional.rb
CHANGED
|
@@ -81,7 +81,7 @@ class CArray
|
|
|
81
81
|
result
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
# @overload conditional(cond, then_fn, else_fn,
|
|
84
|
+
# @overload conditional(cond, then_fn, else_fn, data_type: nil)
|
|
85
85
|
# Returns per-cell `then_fn.call(self[cond])` where `cond` is
|
|
86
86
|
# true and `else_fn.call(self[cond.not])` where it is false.
|
|
87
87
|
# The two callables are applied only to their own subset of
|
|
@@ -91,14 +91,14 @@ class CArray
|
|
|
91
91
|
# Scalar returns from a callable (e.g. `->(v) { 0 }`) broadcast
|
|
92
92
|
# to the subset shape. The result `data_type` is the promotion
|
|
93
93
|
# of the two subset results via `CArray.result_type`, or
|
|
94
|
-
# `
|
|
94
|
+
# `data_type` when given. Masked cells in `cond` propagate to
|
|
95
95
|
# `UNDEF` in the result.
|
|
96
96
|
#
|
|
97
97
|
# @param cond [CArray] boolean selector; same shape as `self`.
|
|
98
98
|
# @param then_fn [#call] callable applied to `self[cond]`.
|
|
99
99
|
# @param else_fn [#call] callable applied to `self[cond.not]`.
|
|
100
|
-
# @param
|
|
101
|
-
#
|
|
100
|
+
# @param data_type [Symbol, Integer, nil] override for the result
|
|
101
|
+
# data type.
|
|
102
102
|
# @return [CArray] new array with the same shape as `self`.
|
|
103
103
|
# @raise [ArgumentError] when `cond` is not a same-shape
|
|
104
104
|
# boolean CArray.
|
|
@@ -108,7 +108,7 @@ class CArray
|
|
|
108
108
|
# ->(v) { v.log }, # domain-safe: only positive cells
|
|
109
109
|
# ->(v) { -v })
|
|
110
110
|
# # => [2.0, 1.0, -0.0, 0.0, 0.6931..., 1.0986...]
|
|
111
|
-
def conditional (cond, then_fn, else_fn,
|
|
111
|
+
def conditional (cond, then_fn, else_fn, data_type: nil)
|
|
112
112
|
unless cond.is_a?(CArray) && cond.boolean? && cond.shape == self.shape
|
|
113
113
|
raise ArgumentError,
|
|
114
114
|
"conditional: cond must be a boolean CArray with same shape as self"
|
|
@@ -123,13 +123,13 @@ class CArray
|
|
|
123
123
|
# to the subset shape; wrap it here so the scatter step below sees a
|
|
124
124
|
# same-length CArray.
|
|
125
125
|
unless y_then.is_a?(CArray)
|
|
126
|
-
y_then = CArray.new(
|
|
126
|
+
y_then = CArray.new(data_type || CArray.result_type(y_then), x_then.shape).fill(y_then)
|
|
127
127
|
end
|
|
128
128
|
unless y_else.is_a?(CArray)
|
|
129
|
-
y_else = CArray.new(
|
|
129
|
+
y_else = CArray.new(data_type || CArray.result_type(y_else), x_else.shape).fill(y_else)
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
-
dt =
|
|
132
|
+
dt = data_type || CArray.result_type(y_then, y_else)
|
|
133
133
|
out = CArray.new(dt, self.shape)
|
|
134
134
|
out[cond] = y_then
|
|
135
135
|
out[cond.not] = y_else
|
|
@@ -141,7 +141,7 @@ class CArray
|
|
|
141
141
|
out
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
-
# @overload select(condlist, choicelist, default: 0,
|
|
144
|
+
# @overload select(condlist, choicelist, default: 0, data_type: nil)
|
|
145
145
|
# Multi-way ternary select: for each cell, picks the value from
|
|
146
146
|
# the first `choicelist[k]` whose matching `condlist[k]` is true,
|
|
147
147
|
# falling back to `default` when no condition holds. When several
|
|
@@ -151,15 +151,15 @@ class CArray
|
|
|
151
151
|
# Each `choicelist[k]` is either a same-shape CArray or a scalar
|
|
152
152
|
# broadcast to every cell. The result `data_type` is the promotion
|
|
153
153
|
# of every choice plus `default` via `CArray.result_type`, or
|
|
154
|
-
# `
|
|
154
|
+
# `data_type` when given.
|
|
155
155
|
#
|
|
156
156
|
# @param condlist [Array<CArray>] boolean selectors.
|
|
157
157
|
# @param choicelist [Array<CArray, Numeric, Object>] values, one
|
|
158
158
|
# per condition (same length as `condlist`).
|
|
159
159
|
# @param default [CArray, Numeric, Object] value written where no
|
|
160
160
|
# condition holds.
|
|
161
|
-
# @param
|
|
162
|
-
#
|
|
161
|
+
# @param data_type [Symbol, Integer, nil] override for the result
|
|
162
|
+
# data type.
|
|
163
163
|
# @return [CArray] new array with the shape of `condlist[0]`.
|
|
164
164
|
# @raise [ArgumentError] on size mismatch, empty `condlist`, or a
|
|
165
165
|
# non-boolean / wrong-shape entry in `condlist`.
|
|
@@ -169,7 +169,7 @@ class CArray
|
|
|
169
169
|
# [-x, x * 10],
|
|
170
170
|
# default: 999)
|
|
171
171
|
# # => [5.0, 3.0, -10.0, 0.0, 10.0, 999.0]
|
|
172
|
-
def self.select (condlist, choicelist, default: 0,
|
|
172
|
+
def self.select (condlist, choicelist, default: 0, data_type: nil)
|
|
173
173
|
unless condlist.is_a?(Array) && choicelist.is_a?(Array)
|
|
174
174
|
raise ArgumentError, "select: condlist and choicelist must be Arrays"
|
|
175
175
|
end
|
|
@@ -187,7 +187,7 @@ class CArray
|
|
|
187
187
|
end
|
|
188
188
|
shape = first.shape
|
|
189
189
|
|
|
190
|
-
dt =
|
|
190
|
+
dt = data_type || CArray.result_type(*choicelist, default)
|
|
191
191
|
# `default` can be either a same-shape CArray (per-cell fallback) or a
|
|
192
192
|
# scalar (broadcast to every cell).
|
|
193
193
|
default_full = default.is_a?(CArray) && !default.scalar?
|