Class: CACategoricalIterator

Inherits:
CAIterator show all
Defined in:
lib/carray/categorical_iterator.rb

Overview

A CAIterator over the categories of a CACategorical. CAIterator is the family base (the built-in iterators like CAWindowIterator / CABlockIterator are defined in C); a Ruby Foo < CAIterator supplies its own behaviour and does not lean on the base machinery. Like CASlabIterator, this class defines its own each (over the k categories, yielding each category's member slice), which drives the inherited Enumerable surface; the reduction methods (sum / mean / median / ...) aggregate the groups into length-k arrays. The kernels are per-category slices of an eager, category-contiguous grouped copy. This supersedes the older CAClassIterator.

Instance Attribute Summary collapse

Attributes inherited from CAIterator

#ndim, #shape

Instance Method Summary collapse

Constructor Details

#initialize(value, cat) ⇒ CACategoricalIterator

value : the payload CArray to reduce, one cell per categorical cell. cat : the CACategorical carrying the classification.

Lays the value out category-contiguous by GATHERING it through the categorical's cached grouping plan (the counting sort lives on cat, built once and shared by every payload column and iterator — see CACategorical's grouping-plan note). The plan gives the segment STARTS (reduceat_index) and the group-major permutation (perm = source index at that grouped slot, = the valid prefix of sort_addr); gathering value through perm is the only per-column work. Excluded cells (masked or out-of-vocabulary code) are absent from perm, so they never join a group; the value mask rides the gather into the grouped copy.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/carray/categorical_iterator.rb', line 87

def initialize (value, cat)
  @cat       = cat
  @labels    = cat.labels
  @k         = cat.labels.size
  @value     = value                              # source, kept for #cumsum etc.
  @src_shape = value.shape                        # output shape for #map
  @ndim      = 1                                  # 1-D iterator over k categories
  @shape     = [@k]

  if value.elements == cat.elements
    # Flat classifier path (backward compat): cat classifies every cell of
    # value one-to-one, so eager counting-sort gather is meaningful.  This is
    # what all no-axis reductions consume — case C (all cells collapse into k
    # buckets) plus case B interpreted flatly.
    # category_sizes IS the per-group cell counts (what #elements returns);
    # the segment STARTS are its cached exclusive prefix scan (cat.reduceat_
    # index): offsets[c] = sum of counts[0...c]. Both come off the shared
    # plan, so the counting sort is not repeated here.
    @elements = cat.category_sizes.int64
    nvalid    = @elements.sum
    @offsets  = cat.reduceat_index                # cached segment STARTS (int64[k])
    # Group-major source indices = the valid prefix of the cached sort_addr.
    # With no classified cell the prefix is empty (and slicing a length-0
    # sort_addr would be out of range), so take the empty permutation directly.
    @perm     = nvalid > 0 ? cat.sort_addr[0...nvalid] : CArray.int64(0)
    @codes    = cat.codes.reshape(cat.elements)   # flat codes view (map re-walk / weights)
    # Gather value into category-contiguous order via the cached permutation
    # and materialise (the reduceat kernels read the grouped buffer's raw ptr,
    # so it must be a contiguous entity, not the selection view); the value
    # mask rides the gather. Payload-dependent, so this is the only part
    # rebuilt per column. With no classified cell (empty / all-excluded) there
    # is nothing to gather — an empty index into an empty source is out of
    # range — so build the empty grouped buffer directly.
    @grouped  = nvalid > 0 ? value.reshape(value.elements)[@perm].copy
                           : CArray.new(value.data_type, [0])
    @empty    = CArray.new(@grouped.data_type, [0])
  else
    # Shape mismatch: only per-fiber axis: dispatch could still work.  With a
    # 1-D value there is no fiber structure to broadcast into, so a mismatch
    # is unrecoverable (preserves the old strict check).  For higher-rank
    # value, defer validation to reduce time — check only that cat.ndim fits
    # one of the 3 axis: cases;
    # any no-axis reduce called on this iterator will surface the mismatch
    # because @grouped stays undefined.
    if value.ndim == 1 ||
       ! [1, value.ndim - 1, value.ndim].include?(cat.ndim)
      raise ArgumentError,
            "group_by_category: value.elements (#{value.elements}) != " \
            "cat.elements (#{cat.elements})" +
            (value.ndim == 1 ? "" :
              ". For per-fiber reduce use `.sum(axis: k)`; cat.ndim=" \
              "#{cat.ndim} must be 1 (case A), #{value.ndim} (case B), " \
              "or #{value.ndim - 1} (band-only) for h.ndim=#{value.ndim}.")
    end
  end
  self
end

Instance Attribute Details

#labelsArray (readonly)

Returns the category vocabulary the results are aligned to.

Returns:



162
163
164
# File 'lib/carray/categorical_iterator.rb', line 162

def labels
  @labels
end

Instance Method Details

#accumulateCArray #accumulate(axis:) ⇒ CArray

Overloads:

  • #accumulateCArray

    Returns per-category sums folded in the value's own data type, wrapping at its width, as the core accumulate does. This is the exact in-type fold: sum reads its answer off a float64 moment and casts back, so it loses the low bits of a wide integer payload and does not wrap. An empty or fully-masked category accumulates the empty set, the additive identity 0 (unmasked).

    Returns:

  • #accumulate(axis:) ⇒ CArray

    Per-fiber per-category in-type sums along axis. Output shape = [K, ...source.shape without axis].

    Parameters:

    • axis (Integer)

      reduce axis of the source value.

    Returns:



293
294
295
296
# File 'lib/carray/categorical_iterator.rb', line 293

def accumulate(axis: nil)
  return axis_by_masked_copy(axis, :accumulate, core_reduce_type(:accumulate)) if axis
  per_category(core_reduce_type(:accumulate)) { |s| s.accumulate }
end

#allCArray

Returns the per-category all as boolean (matching CArray#all): true iff every present value is truthy (empty category -> true, vacuously). The value data type must be boolean, as for CArray#all.

Returns:



435
436
437
438
# File 'lib/carray/categorical_iterator.rb', line 435

def all
  aa = all_any
  aa ? aa[:all] : per_category(CA_BOOLEAN) { |s| s.all }
end

#anyCArray

Returns the per-category any as boolean (matching CArray#any): true iff some present value is truthy (empty category -> false). The value data type must be boolean, as for CArray#any.

Returns:



445
446
447
448
# File 'lib/carray/categorical_iterator.rb', line 445

def any
  aa = all_any
  aa ? aa[:any] : per_category(CA_BOOLEAN) { |s| s.any }
end

#count(v = <none>) ⇒ CArray #count(axis:) ⇒ CArray

Overloads:

  • #count(v = <none>) ⇒ CArray

    Per-category count, mirroring CArray#count per group. No argument returns #count_not_masked (present cells); count(UNDEF) returns #count_masked; count(v) counts cells whose value equals v.

    Returns:

  • #count(axis:) ⇒ CArray

    No-arg + axis: = per-fiber per-category count_not_masked (shape [K, ...band]). count(v, axis:) (value equality) and count(UNDEF, axis:) are not implemented; use them without axis:.

    Parameters:

    • axis (Integer)

    Returns:



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/carray/categorical_iterator.rb', line 224

def count (*args, axis: nil)
  if axis
    return count_not_masked(axis: axis) if args.empty?
    raise NotImplementedError,
          "CACategoricalIterator#count(v, axis:) is not implemented — " \
          "value-equality count is available without axis:."
  end
  return count_not_masked if args.empty?
  # Delegate per group to CArray#count (handles count(UNDEF) -> masked count and
  # count(v) alike, with core's exact data type equality). The group slice is a
  # CABlock, whose own #count is the block geometry accessor, so dispatch
  # CArray#count explicitly. (Not fused: a value-equality reduceat would have
  # to reproduce core's cross-type / out-of-range equality exactly.)
  cnt = CArray.instance_method(:count)
  per_category(CA_INT64) { |s| cnt.bind_call(s, *args) }
end

#count_maskedCArray #count_masked(axis:) ⇒ CArray

Overloads:

  • #count_maskedCArray

    Returns the per-category count of masked (missing) values as int64. Empty categories are 0.

    Returns:

  • #count_masked(axis:) ⇒ CArray

    Not implemented; call it without axis:.

    Parameters:

    • axis (Integer)

    Returns:



249
250
251
252
253
254
255
256
257
# File 'lib/carray/categorical_iterator.rb', line 249

def count_masked(axis: nil)
  if axis
    raise NotImplementedError,
          "CACategoricalIterator#count_masked(axis:) is not implemented — " \
          "call it without axis:."
  end
  m = moments
  m ? @elements - m[:count] : per_category(CA_INT64) { |s| s.count_masked }
end

#count_not_maskedCArray #count_not_masked(axis:) ⇒ CArray

Overloads:

  • #count_not_maskedCArray

    Returns the per-category count of present (non-masked) values as int64 — the denominator the value reductions actually divide by. Equals #elements unless the value carries a mask. A count is always defined, so an empty category is 0 (never masked).

    Returns:

  • #count_not_masked(axis:) ⇒ CArray

    Per-fiber per-category count of present (non-masked) values along axis (int64, shape [K, ...band]). Empty cells are 0.

    Parameters:

    • axis (Integer)

    Returns:



207
208
209
210
211
# File 'lib/carray/categorical_iterator.rb', line 207

def count_not_masked(axis: nil)
  return axis_moments(axis)[:count] if axis
  m = moments
  m ? m[:count] : per_category(CA_INT64) { |s| s.count_not_masked }
end

#each({ |members| ... }) {|members| ... } ⇒ Enumerator, self

Yields each category's members (a CArray slice of the grouped copy, in #labels order; an empty category yields an empty array). Without a block, returns an Enumerator. This is the own iteration that drives the inherited Enumerable methods (map / count / to_a / ...); it does not use the CAIterator base each / kernel_at_addr path.

Yield Parameters:

Returns:

  • (Enumerator, self)


153
154
155
156
157
# File 'lib/carray/categorical_iterator.rb', line 153

def each
  return to_enum(:each) unless block_given?
  @k.times { |c| yield group_slice(c) }
  self
end

#elementsCArray Also known as: group_sizes

Returns per-group cell counts (classified cells, including value-masked ones; = cat.category_sizes), a length-ngroups CArray aligned to #labels. The CAIterator count-family member — CArray#elements (structural, mask-independent) lifted per group.

Returns:



178
179
180
# File 'lib/carray/categorical_iterator.rb', line 178

def elements
  @elements
end

#inspectString

Returns a compact one-line summary — the group count, the label vocabulary, and the per-group cell counts — instead of dumping the internal grouped/value/codes buffers.

Returns:

  • (String)


191
192
193
194
# File 'lib/carray/categorical_iterator.rb', line 191

def inspect
  "#<#{self.class} ngroups=#{@k} labels=#{@labels.inspect} " \
  "elements=#{@elements.to_a.inspect}>"
end

#map(data_type: nil) {|members| ... } ⇒ CArray

Group-wise element-wise transform, mirroring CArray#map_slab. The block receives each category's members and returns either a same-length CArray (scattered back cell for cell) or a scalar (broadcast over the group's cells). Returns a NEW CArray shaped like the source value; the original is not modified (value[] = grp.map { ... } for in-place). Excluded cells (in no category) are UNDEF in the result.

Yield Parameters:

Returns:

  • (CArray)

    shaped like the source value

Raises:

  • (LocalJumpError)


642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
# File 'lib/carray/categorical_iterator.rb', line 642

def map (data_type: nil)
  raise LocalJumpError, "no block given (yield)" unless block_given?
  dt = data_type || @grouped.data_type
  # Apply the block per category, assembled in grouped (category-contiguous)
  # order: a same-length result scatters cell for cell, a scalar broadcasts.
  transformed = CArray.new(dt, [@grouped.elements])
  @k.times do |c|
    lo = @offsets[c]
    hi = (c + 1 < @k) ? @offsets[c + 1] : @grouped.elements
    transformed[lo...hi] = yield(@grouped[lo...hi]) if hi > lo
  end
  # Scatter back to source positions via the permutation (grouped-order source
  # indices). Excluded cells are absent from perm and stay UNDEF.
  out = CArray.new(dt, @src_shape)
  out[] = UNDEF
  out.reshape(@codes.elements)[perm] = transformed
  out
end

#maxCArray #max(axis:) ⇒ CArray

Overloads:

  • #maxCArray

    Returns per-category maxima in the value data type. Empty categories are MASKED.

    Returns:

  • #max(axis:) ⇒ CArray

    Per-fiber per-category maxima along axis (h's data type, masked where empty).

    Parameters:

    • axis (Integer)

    Returns:



306
307
308
309
310
# File 'lib/carray/categorical_iterator.rb', line 306

def max(axis: nil)
  return axis_moments(axis)[:max] if axis
  m = moments
  m ? m[:max] : per_category(core_reduce_type(:max)) { |s| s.max }
end

#max_addrCArray

Per-category flat source address of the maximum. See #min_addr.

Returns:



529
530
531
# File 'lib/carray/categorical_iterator.rb', line 529

def max_addr
  group_addr(max_index)
end

#max_indexCArray

Per-category group-local index of the maximum. See #min_index.

Returns:



511
512
513
514
# File 'lib/carray/categorical_iterator.rb', line 511

def max_index
  am = arg_minmax
  am ? am[:max] : per_category(CA_INT64) { |s| s.max_index }
end

#meanCArray #mean(axis:) ⇒ CArray

Overloads:

  • #meanCArray

    Returns per-category means as float64. Empty categories are MASKED.

    Returns:

  • #mean(axis:) ⇒ CArray

    Per-fiber per-category means (float64, empty group cells MASKED).

    Parameters:

    • axis (Integer)

    Returns:



333
334
335
336
337
338
339
340
341
# File 'lib/carray/categorical_iterator.rb', line 333

def mean(axis: nil)
  return axis_mean(axis) if axis
  m = moments
  return per_category(core_reduce_type(:mean)) { |s| s.mean } unless m
  cnt = m[:count]
  out = m[:sum] / cnt.float64      # count 0 -> NaN, masked next
  out[cnt.eq(0)] = UNDEF           # empty / all-masked category -> MASKED
  out
end

#medianCArray

Returns per-category medians as float64. Empty categories are MASKED.

Returns:



346
347
348
349
# File 'lib/carray/categorical_iterator.rb', line 346

def median(axis: nil)
  axis_order_stat_defer!(:median) if axis
  percentile(50.0)
end

#minCArray #min(axis:) ⇒ CArray

Overloads:

  • #minCArray

    Returns per-category minima in the value data type. Empty categories are MASKED.

    Returns:

  • #min(axis:) ⇒ CArray

    Per-fiber per-category minima along axis (h's data type, masked where empty).

    Parameters:

    • axis (Integer)

    Returns:



320
321
322
323
324
# File 'lib/carray/categorical_iterator.rb', line 320

def min(axis: nil)
  return axis_moments(axis)[:min] if axis
  m = moments
  m ? m[:min] : per_category(core_reduce_type(:min)) { |s| s.min }
end

#min_addrCArray

Per-category flat source address of the minimum — which cell of the source value holds it, matching CArray#min_addr per group. Unlike #min_index (the group-local rank) this indexes back into the original array (value.reshape(value.elements)[grp.min_addr]). Empty categories MASKED.

Returns:



522
523
524
# File 'lib/carray/categorical_iterator.rb', line 522

def min_addr
  group_addr(min_index)
end

#min_indexCArray

Per-category group-local index of the minimum — the position within the category's members (source order) — matching CArray#min_index per group. Empty / all-masked categories are MASKED. Single-pass fused reduceat for numeric values; per-group fallback otherwise.

Returns:



503
504
505
506
# File 'lib/carray/categorical_iterator.rb', line 503

def min_index
  am = arg_minmax
  am ? am[:min] : per_category(CA_INT64) { |s| s.min_index }
end

#minmaxArray<CArray> #minmax(axis:) ⇒ Array<CArray>

Overloads:

  • #minmaxArray<CArray>

    Returns the per-category [min, max] pair (each a length-k CArray in the value data type; empty categories MASKED), matching CArray#minmax. Both come from the single cached moments pass.

    Returns:

  • #minmax(axis:) ⇒ Array<CArray>

    Per-fiber [min_ca, max_ca] along axis (each shape [K, ...band], h's data type, empty group cells MASKED). Ruby Array of two CArrays, not stacked.

    Parameters:

    • axis (Integer)

    Returns:



462
463
464
465
# File 'lib/carray/categorical_iterator.rb', line 462

def minmax(axis: nil)
  return [min(axis: axis), max(axis: axis)] if axis
  [min, max]
end

#ngroupsInteger

Returns the number of groups (= labels.size); the length of every per-group result CArray the reductions return.

Returns:

  • (Integer)


168
169
170
# File 'lib/carray/categorical_iterator.rb', line 168

def ngroups
  @k
end

#cumsumCArray #cumprodCArray #cummaxCArray #cumminCArray #cumcountCArray

Overloads:

  • #cumsumCArray

    Per-category inclusive running sum (float64), source-shaped.

    Returns:

  • #cumprodCArray

    Per-category inclusive running product (float64), source-shaped.

    Returns:

  • #cummaxCArray

    Per-category inclusive running maximum (value data type), source-shaped.

    Returns:

  • #cumminCArray

    Per-category inclusive running minimum (value data type), source-shaped.

    Returns:

  • #cumcountCArray

    Per-category 1-based within-category ordinal (int64), source-shaped.

    Returns:



694
695
696
# File 'lib/carray/categorical_iterator.rb', line 694

[:cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |op|
  define_method(op) { scan(op) }
end

#percentile(p) ⇒ CArray

Returns the per-category p-th percentile as float64 (p in 0..100, :linear interpolation, matching CArray#percentile). Empty categories are MASKED. Order statistics need every value of a group held together — this is the reduceat that only the eager grouped copy can serve.

Parameters:

  • p (Numeric)

    percentile in 0..100.

Returns:



358
359
360
361
362
363
364
365
366
# File 'lib/carray/categorical_iterator.rb', line 358

def percentile (p, axis: nil)
  axis_order_stat_defer!(:percentile) if axis
  unless MONOID_TYPES.include?(@grouped.data_type)
    return per_category(core_reduce_type(:percentile, p)) { |s| s.percentile(p) }
  end
  out = CArray.float64(@k)
  @grouped.send(:__reduceat_percentile__, @offsets, p.to_f, out)
  out
end

#prodCArray #prod(axis:) ⇒ CArray

Overloads:

  • #prodCArray

    Returns per-category products as float64 (matching CArray#prod). An empty / fully-masked category is 1.0 (the multiplicative identity). Single-pass reduceat for numeric values; per-group fallback otherwise.

    Returns:

  • #prod(axis:) ⇒ CArray

    Per-fiber per-category products (float64, shape [K, ...band]). Empty group cells 1.0 (identity).

    Parameters:

    • axis (Integer)

    Returns:



422
423
424
425
426
427
428
# File 'lib/carray/categorical_iterator.rb', line 422

def prod(axis: nil)
  return axis_prod(axis) if axis
  return per_category(core_reduce_type(:prod)) { |s| s.prod } unless MONOID_TYPES.include?(@grouped.data_type)
  out = CArray.float64(@k)
  @grouped.send(:__reduceat_prod__, @offsets, out)
  out
end

#quantileArray<CArray>

Returns the per-category five-number summary [min, Q1, median, Q3, max] as five length-k float64 CArrays (matching CArray#quantile): the percentiles at 0 / 25 / 50 / 75 / 100. Empty / all-masked categories are MASKED. For a single fraction q in 0..1 use percentile(q * 100).

Returns:



374
375
376
377
378
379
380
381
# File 'lib/carray/categorical_iterator.rb', line 374

def quantile
  unless MONOID_TYPES.include?(@grouped.data_type)
    return [0, 25, 50, 75, 100].map { |p| percentile(p) }
  end
  outs = Array.new(5) { CArray.float64(@k) }
  @grouped.send(:__reduceat_quantile__, @offsets, *outs)
  outs
end

#reduce({ |members| ... }) {|members| ... } ⇒ CArray #reduce(init) ⇒ CArray

Overloads:

  • #reduce({ |members| ... }) {|members| ... } ⇒ CArray

    Custom per-category reduction (the escape hatch for statistics not in the named surface), mirroring CArray#reduce_slab. The block receives each category's members (a CArray) and returns one value per category.

    Yield Parameters:

    Returns:

  • #reduce(init) ⇒ CArray

    Per-category fiber fold: each category's members are folded element by element starting from init.

    Parameters:

    • init (Object)

      initial accumulator.

    Returns:

Raises:

  • (LocalJumpError)


618
619
620
621
622
623
624
625
626
627
628
629
630
631
# File 'lib/carray/categorical_iterator.rb', line 618

def reduce (*args, data_type: nil, &blk)
  raise LocalJumpError, "no block given (yield)" unless blk
  dt = data_type || CA_OBJECT
  if args.empty?
    per_category(dt) { |s| blk.call(s) }
  else
    init = args[0]
    per_category(dt) { |s|
      acc = init
      s.each { |e| acc = blk.call(acc, e) }
      acc
    }
  end
end

#sort_addrCArray

Per-category sort by flat source address. Returns a length-nvalid (= elements.sum) int64 CArray of the flat SOURCE addresses that sort each category's members, in group-major order: segment c holds category c's source addresses in ascending-value order, segments concatenated in #labels order. So value.reshape(value.elements)[grp.sort_addr] yields the values grouped and sorted within each group, and splitting by the #elements prefix sum gives per-group. Excluded cells (in no category) are omitted. A masked value sorts to the tail of its segment (as CArray#sort sends masked cells to the end), so with a mask the first address is the minimum but the last is the masked cell, not the maximum.

Unlike #min_index / #max_index (group-local rank), this indexes back into the original array. There is no group-local sort surface: a group-local rank order is weak (the grouped copy is already category-contiguous), so only the source-address form is offered, mirroring #min_addr vs the skipped group-local min_index-into-source.

Returns:

  • (CArray)

    length-nvalid int64



551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/carray/categorical_iterator.rb', line 551

def sort_addr
  out = CArray.int64(@grouped.elements)
  @k.times do |c|
    lo = @offsets[c]
    hi = (c + 1 < @k) ? @offsets[c + 1] : @grouped.elements
    next unless hi > lo
    # View-local sort order of the segment (0..size-1), lifted to grouped
    # slots, then mapped back to source addresses via perm.
    out[lo...hi] = perm[@grouped[lo...hi].sort_addr + lo]
  end
  out
end

#stddevCArray

Returns per-category SAMPLE standard deviation (ddof=1) as float64. Matches CArray#stddev per group (empty / all-masked MASKED, single-value 0.0).

Returns:



405
406
407
408
409
410
# File 'lib/carray/categorical_iterator.rb', line 405

def stddev(axis: nil)
  return axis_by_masked_copy(axis, :stddev) if axis
  m = moments
  return per_category(core_reduce_type(:stddev)) { |s| s.stddev } unless m
  variance.sqrt                    # sqrt propagates the n=0 mask
end

#stddevpCArray #stddevp(axis:) ⇒ CArray

Overloads:

  • #stddevpCArray

    Per-category POPULATION standard deviation (ddof=0) as float64.

    Returns:

  • #stddevp(axis:) ⇒ CArray

    Per-fiber per-category population stddev (float64, empty group cells MASKED).

    Parameters:

    • axis (Integer)

    Returns:



490
491
492
493
494
495
# File 'lib/carray/categorical_iterator.rb', line 490

def stddevp(axis: nil)
  return axis_by_masked_copy(axis, :stddevp) if axis
  m = moments
  return per_category(core_reduce_type(:stddevp)) { |s| s.stddevp } unless m
  variancep.sqrt
end

#sumCArray #sum(axis:) ⇒ CArray

Overloads:

  • #sumCArray

    Returns per-category sums in the data type CArray#sum promotes the value to (float64 for an integer value). accumulate is the same fold kept in the value's own type. An empty or fully-masked category sums the empty set, which is the additive identity 0 (unmasked) — the same contract as CArray#sum on an empty / all-masked array.

    Returns:

  • #sum(axis:) ⇒ CArray

    Returns per-category sums per fiber along axis. Cat may be 1-D (case A, broadcasts across band axes), same rank as source (case B, per-fiber independent classifier), or one rank less (band-only, constant along reduce axis). Output shape = [K, ...source.shape without axis].

    Parameters:

    • axis (Integer)

      reduce axis of the source value.

    Returns:



273
274
275
276
277
278
# File 'lib/carray/categorical_iterator.rb', line 273

def sum(axis: nil)
  return axis_sum(axis) if axis
  m = moments
  return per_category(core_reduce_type(:sum)) { |s| s.sum } unless m
  m[:sum].copy                    # the moments sum IS the core fold (empty -> 0.0)
end

#varianceCArray

Returns per-category SAMPLE variance (ddof=1) as float64. Matches CArray#variance per group: an empty or fully-masked category is MASKED, a single-value category is 0.0 (CArray's n=1 contract), n>=2 is the sample variance.

Returns:



389
390
391
392
393
394
395
396
397
398
# File 'lib/carray/categorical_iterator.rb', line 389

def variance(axis: nil)
  return axis_by_masked_copy(axis, :variance) if axis
  m = moments
  return per_category(core_reduce_type(:variance)) { |s| s.variance } unless m
  cnt   = m[:count]
  means = m[:sum] / cnt.float64    # per-segment mean (garbage where count 0/1,
  out   = CArray.float64(@k)       #   ignored by the kernel's n<2 guards)
  @grouped.send(:__reduceat_variance__, @offsets, means, cnt, out)
  out
end

#variancepCArray

Per-category POPULATION variance (ddof=0) as float64, matching CArray#variancep: empty / all-masked -> MASKED, single value -> 0.0. Derived from the sample variance (variancep = variance * (n-1) / n), so it reuses the centred two-pass kernel with no extra walk.

Returns:



473
474
475
476
477
478
479
480
481
# File 'lib/carray/categorical_iterator.rb', line 473

def variancep(axis: nil)
  return axis_by_masked_copy(axis, :variancep) if axis
  m = moments
  return per_category(core_reduce_type(:variancep)) { |s| s.variancep } unless m
  cnt = m[:count]
  vp  = variance * (cnt - 1).float64 / cnt.float64
  vp[cnt.eq(0)] = UNDEF                 # empty / all-masked stays masked
  vp
end

#wmean(weights) ⇒ CArray #wmean(weights, axis:) ⇒ CArray

Overloads:

  • #wmean(weights) ⇒ CArray

    Per-category weighted mean as float64, matching CArray#wmean. Empty category -> MASKED; a present category whose weights sum to zero -> NaN (core's 0/0 contract).

    Parameters:

    Returns:

  • #wmean(weights, axis:) ⇒ CArray

    Per-fiber per-category weighted mean along axis. Same weights-shape contract as #wsum (weights.shape == source.shape). Empty cell → MASKED; a present cell whose weights sum to zero → NaN (0/0 core contract).

    Parameters:

    • weights (CArray)
    • axis (Integer)

    Returns:



600
601
602
603
604
605
# File 'lib/carray/categorical_iterator.rb', line 600

def wmean (weights, axis: nil)
  return axis_wsum_wmean(weights, axis)[1] if axis
  wg = scatter_weights(weights)
  return kernel_weighted(wg)[1] if MONOID_TYPES.include?(@grouped.data_type)
  fold_weighted(wg, UNDEF) { |v, ws| v.wmean(ws) }
end

#wsum(weights) ⇒ CArray #wsum(weights, axis:) ⇒ CArray

Overloads:

  • #wsum(weights) ⇒ CArray

    Per-category weighted sum as float64, matching CArray#wsum. weights is a per-cell weight CArray in the source order (same elements as the value). Empty / all-masked category -> 0.0 (the additive identity). A cell is skipped iff its value OR its weight is masked (core's contract).

    Parameters:

    Returns:

  • #wsum(weights, axis:) ⇒ CArray

    Per-fiber per-category weighted sum along axis. weights must have shape == source.shape (rev3 requires explicit broadcast; wrap 1-D or band-shape weights via .broadcast_to(*source.shape) at the call site). Empty group cell → 0.0 (identity). Mask contract: cell contributes iff value AND weight are present.

    Parameters:

    • weights (CArray)
    • axis (Integer)

    Returns:



580
581
582
583
584
585
# File 'lib/carray/categorical_iterator.rb', line 580

def wsum (weights, axis: nil)
  return axis_wsum_wmean(weights, axis)[0] if axis
  wg = scatter_weights(weights)
  return kernel_weighted(wg)[0] if MONOID_TYPES.include?(@grouped.data_type)
  fold_weighted(wg, 0.0) { |v, ws| v.wsum(ws) }
end