Class: CAGroupIterator

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

Overview

Let the C [] type gate (ext/ca_group_iter.c) recognise these classes without a kind_of on every index. Registered once on first load of this file, which happens either eagerly (a axis_group / AxisGroup call) or lazily from the C gate itself the first time it meets a fixlen-surface CArray index (see ca_argv_has_group). CACategorical is the classifier, so it must be defined before the register call. CAGroupIterator (C-defined in ext/ca_group_iter.c) — the reduction dispatcher returned by the [] group gate. Its scatterable reductions (sum / prod / mean / min / max / variance / stddev / variancep / stddevp / count / count_not_masked / all / any) bind in C to one driver; the rest of the common iterator surface that composes cheaply from those is added here. CAGroupIterator subclasses CAIterator, but the class itself is created in C, so the reopening below carries no superclass for YARD to read.

Instance Method Summary collapse

Methods inherited from CAIterator

#accumulate, #count_not_masked, #max, #max_addr, #mean, #min, #min_addr, #prod, #stddev, #stddevp, #sum, #variance, #variancep

Instance Method Details

#__all_folded__ ⇒ Object



358
# File 'lib/carray/axis_group.rb', line 358

alias __all_folded__ all

#__any_folded__ ⇒ Object



359
# File 'lib/carray/axis_group.rb', line 359

alias __any_folded__ any

#__count_present__ ⇒ Object

The C dispatcher every reduction shares takes keywords only, so the value-equality and masked forms the base declares -- count(v), count(UNDEF) -- arrived as "wrong number of arguments", which reads as a method that does not take one rather than one whose engine has no such fold. Both are composed here instead, out of folds the engine does have.



335
# File 'lib/carray/axis_group.rb', line 335

alias __count_present__ count

#all ⇒ CArray

Per-group all over a boolean payload.

Returns:



364
365
366
367
# File 'lib/carray/axis_group.rb', line 364

def all (**kw)
  boolean_payload!(:all)
  __all_folded__(**kw)
end

#any ⇒ CArray

Per-group any over a boolean payload.

Returns:



372
373
374
375
# File 'lib/carray/axis_group.rb', line 372

def any (**kw)
  boolean_payload!(:any)
  __any_folded__(**kw)
end

#count ⇒ Object #count(UNDEF) ⇒ Object #count(v) ⇒ CArray

Overloads:

  • #count ⇒ Object

    Per-group count of present (non-masked) cells.

  • #count(UNDEF) ⇒ Object

    Per-group count of masked cells, as #count_masked.

  • #count(v) ⇒ CArray

    Per-group count of cells equal to v.

    Returns:



344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/carray/axis_group.rb', line 344

def count (*args, **kw)
  return __count_present__(**kw) if args.empty?
  if args.size > 1
    raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..1)"
  end
  v = args.first
  return count_masked(**kw) if v.equal?(UNDEF)
  # a masked cell equals nothing, and eq marks it UNDEF; drop that to false
  # so it is simply not counted
  hit = value.eq(v)
  hit = hit.strip_mask(false) if hit.has_mask?
  self.class.__build__(hit.int64, spec).sum(**kw).int64
end

#count_masked(**kw) ⇒ Object

Per-group count of value-masked cells = elements - count_not_masked.



385
386
387
# File 'lib/carray/axis_group.rb', line 385

def count_masked (**kw)
  elements(**kw) - count_not_masked(**kw)
end

#cumcount(axis: :group) ⇒ CArray

Per-group 1-based within-group ordinal (int64), source-shaped: the first member of a group is 1, the next 2, ... (a running count of the group's members up to and including the cell). Without :group delegates to the plain value cumcount (also a 1-based cumulative count of present cells).

Returns:



492
493
494
# File 'lib/carray/axis_group.rb', line 492

def cumcount (**kw)
  scan_op(:cumcount, kw)
end

#cummax(axis: :group) ⇒ CArray

Per-group inclusive running maximum, source-shaped, in the source data type (extrema do not grow magnitude, so the data type is preserved). The first member of a group emits its own value. Without :group delegates to value cummax.

Returns:



474
475
476
# File 'lib/carray/axis_group.rb', line 474

def cummax (**kw)
  scan_op(:cummax, kw)
end

#cummin(axis: :group) ⇒ CArray

Per-group inclusive running minimum, source-shaped, in the source data type. Without :group delegates to value cummin.

Returns:



482
483
484
# File 'lib/carray/axis_group.rb', line 482

def cummin (**kw)
  scan_op(:cummin, kw)
end

#cumprod(axis: :group) ⇒ CArray

Per-group inclusive running product (float64), source-shaped. float64 like cumsum since the product grows. Without :group delegates to value cumprod.

Returns:



464
465
466
# File 'lib/carray/axis_group.rb', line 464

def cumprod (**kw)
  scan_op(:cumprod, kw)
end

#cumsum(axis: :group) ⇒ CArray

Per-group inclusive running sum (float64), source-shaped. Without :group delegates to the plain value cumsum.

Returns:



456
457
458
# File 'lib/carray/axis_group.rb', line 456

def cumsum (**kw)
  scan_op(:cumsum, kw)
end

#each({ |members| ... }) ⇒ Object

Yields each group's members (a CArray). A flat grouping yields per group (composite category); a band-preserving grouping yields per (band position, composite category), band-major then group order. Without a block returns an Enumerator.



555
556
557
558
559
560
561
# File 'lib/carray/axis_group.rb', line 555

def each (&block)
  ccat, _kd, gslots, bslots, gaxes = composite_layout
  return value.group_by_category(ccat).each(&block) if bslots.empty?
  return to_enum(:each) unless block
  each_band_block(ccat, gslots, bslots, gaxes) { |_vi, _oi, _co, gi| gi.each(&block) }
  self
end

#elements(**kw) ⇒ Object

Per-group classified cell count (mask-independent) = count on the mask-stripped value, so every classified cell is counted regardless of the value mask (unlike count / count_not_masked, which count present cells).



380
381
382
# File 'lib/carray/axis_group.rb', line 380

def elements (**kw)
  self.class.__build__(value.value, spec).count(**kw)
end

#map(data_type: nil) ⇒ CArray

Group-wise element-wise transform back to a source-shaped array. Excluded cells (in no group) are UNDEF. Any grouping (single or composite, flat or band-preserving).

Returns:

Raises:

  • (LocalJumpError)


568
569
570
571
572
573
574
575
576
577
578
# File 'lib/carray/axis_group.rb', line 568

def map (data_type: nil, &block)
  raise LocalJumpError, "no block given (yield)" unless block
  ccat, _kd, gslots, bslots, gaxes = composite_layout
  return value.group_by_category(ccat).map(data_type: data_type, &block) if bslots.empty?
  dt  = data_type || value.data_type
  out = CArray.new(dt, value.shape)
  each_band_block(ccat, gslots, bslots, gaxes) do |val_idx, _oi, _co, gi|
    out[*val_idx] = gi.map(data_type: dt, &block)
  end
  out
end

#max_index ⇒ Object

Raises:

  • (NotImplementedError)


409
410
411
412
413
414
# File 'lib/carray/axis_group.rb', line 409

def max_index (*)
  raise NotImplementedError,
        "CAGroupIterator has no max_index: a group preserves source order, so " \
        "a within-group index is weak; use max_addr for the winner's flat " \
        "source address (it indexes back into the original array)."
end

#median ⇒ CArray

Per-group median (float64), any grouping. See #percentile.

Returns:



519
520
521
# File 'lib/carray/axis_group.rb', line 519

def median (**kw)
  order_stat(:median, [], kw)
end

#min_index ⇒ Object #max_index ⇒ Object

Overloads:

  • #max_index ⇒ Object

    Not provided for a group iterator: a group preserves source order, so a within-group index is weak (the members are not laid out in a private axis to index into). Use min_addr / max_addr for the winner's flat source address, which indexes back into the original array. Raises NotImplementedError.

    Raises:

    • (NotImplementedError)

Raises:

  • (NotImplementedError)


402
403
404
405
406
407
# File 'lib/carray/axis_group.rb', line 402

def min_index (*)
  raise NotImplementedError,
        "CAGroupIterator has no min_index: a group preserves source order, so " \
        "a within-group index is weak; use min_addr for the winner's flat " \
        "source address (it indexes back into the original array)."
end

#minmax(**kw) ⇒ Object

Per-group [min, max] pair (matching CArray#minmax).



390
391
392
# File 'lib/carray/axis_group.rb', line 390

def minmax (**kw)
  [min(**kw), max(**kw)]
end

#ndim ⇒ Object



326
327
328
# File 'lib/carray/axis_group.rb', line 326

def ndim
  spec.nslots
end

#percentile(*pers) ⇒ CArray+

Per-group percentile(s) (float64), any grouping.

Returns:



526
527
528
# File 'lib/carray/axis_group.rb', line 526

def percentile (*pers, **kw)
  order_stat(:percentile, pers, kw)
end

#quantile ⇒ Array<CArray>

Per-group five-number summary [min, Q1, median, Q3, max], any grouping.

Returns:



533
534
535
# File 'lib/carray/axis_group.rb', line 533

def quantile (**kw)
  order_stat(:quantile, [], kw)
end

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

Overloads:

  • #reduce(init) ⇒ CArray

    Custom per-group reduction (dual form). Output shape = slot order (each group slot -> its k, each band slot -> its length). Any grouping (single or composite, flat or band-preserving).

    Returns:

Raises:

  • (LocalJumpError)


586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/carray/axis_group.rb', line 586

def reduce (*args, data_type: nil, &block)
  raise LocalJumpError, "no block given (yield)" unless block
  ccat, kdims, gslots, bslots, gaxes = composite_layout
  if bslots.empty?
    return value.group_by_category(ccat).reduce(*args, data_type: data_type, &block)
                .reshape(*kdims)
  end
  dt = data_type || CA_OBJECT
  out_shape = spec.slot_meta.map { |m| m[:kind] == :group ? m[:k] : m[:len] }
  out = CArray.new(dt, out_shape)
  each_band_block(ccat, gslots, bslots, gaxes) do |_vi, out_idx, _co, gi|
    out[*out_idx] = gi.reduce(*args, data_type: dt, &block).reshape(*kdims)
  end
  out
end

#shape ⇒ Object Also known as: dim

The shape one value per piece comes back in: a group slot contributes its category count, a band slot its length, in slot order. The base declares these and every other member answers them; this one inherited the readers without anything ever setting the ivars, so it answered nil -- which the base's own rule calls out as the one thing a member must not do ("a clean NotImplementedError, never a wrong answer").



321
322
323
# File 'lib/carray/axis_group.rb', line 321

def shape
  spec.slot_meta.map { |m| m[:kind] == :group ? m[:k] : m[:len] }
end

#sort_addr ⇒ CArray

Per-group sorted flat source addresses (group-major). Any grouping (single or composite group slots, rank-1 or rank-N, flat or band-preserving) is supported; with no :group it is a plain value.sort_addr.

Returns:



542
543
544
545
546
547
548
# File 'lib/carray/axis_group.rb', line 542

def sort_addr (**kw)
  has_group, _ = AxisGroup.parse_axis(kw[:axis])
  return value.sort_addr unless has_group
  ccat, _kd, gslots, bslots, gaxes = composite_layout
  return value.group_by_category(ccat).sort_addr if bslots.empty?
  composite_band_sort_addr(ccat, gslots, bslots, gaxes)
end

#wmean(weights, **kw) ⇒ Object

Weighted mean = Sum(vw) / Sum(w), both over the combined present-set. The value*0 + weights denominator carries the same value|weight mask, so its group-sum is Sum(w) over exactly the cells valueweight used. An empty group (no present pair) is UNDEF (matching CArray#wmean); a present group whose weights sum to zero yields NaN/Inf (core's 0/0).



429
430
431
432
433
434
435
436
437
# File 'lib/carray/axis_group.rb', line 429

def wmean (weights, **kw)
  prod = value * weights
  num  = self.class.__build__(prod, spec).sum(**kw)
  den  = self.class.__build__(value * 0 + weights, spec).sum(**kw)
  cnt  = self.class.__build__(prod, spec).count_not_masked(**kw)
  out  = num / den
  out[cnt.eq(0)] = UNDEF          # no present pair -> masked (empty group)
  out
end

#wsum(weights, **kw) ⇒ Object

Weighted sum: group-sum of value*weight. weights is a per-cell CArray in the source layout (same shape as value); the product carries the combined value|weight mask, so masked cells drop out. Empty group -> 0.0 (identity). No weighted kernel needed -- it is a plain group-sum of a derived array.



420
421
422
# File 'lib/carray/axis_group.rb', line 420

def wsum (weights, **kw)
  self.class.__build__(value * weights, spec).sum(**kw)
end