carray 3.0.4 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +205 -0
- data/carray.gemspec +1 -1
- data/ext/ca_array_pool.c +25 -1
- data/ext/ca_axis_dispatch.c +13 -20
- data/ext/ca_categorical_iterator.c +27 -20
- data/ext/ca_compare.c +4 -4
- data/ext/ca_composite_dispatch.c +9 -9
- data/ext/ca_kernel_iterator.c +6 -6
- data/ext/ca_kernel_iterator.h +112 -83
- data/ext/ca_obj_array.c +69 -19
- data/ext/ca_obj_bincmp.c +1 -0
- data/ext/ca_obj_binop.c +1 -0
- data/ext/ca_obj_bitarray.c +5 -1
- data/ext/ca_obj_bitfield.c +58 -43
- data/ext/ca_obj_byte_swap.c +2 -1
- data/ext/ca_obj_const_string.c +15 -3
- data/ext/ca_obj_face.c +42 -1
- data/ext/ca_obj_field.c +13 -3
- data/ext/ca_obj_grid.c +52 -5
- data/ext/ca_obj_monop.c +46 -10
- data/ext/ca_obj_object.c +64 -12
- data/ext/ca_obj_record.c +3 -0
- data/ext/ca_obj_reduce.c +4 -0
- data/ext/ca_obj_refer.c +18 -9
- data/ext/ca_obj_roll.c +26 -6
- data/ext/ca_obj_select.c +34 -5
- data/ext/ca_obj_shift.c +31 -7
- data/ext/ca_obj_stack.c +1 -0
- data/ext/ca_obj_stride.c +28 -19
- data/ext/ca_obj_string.c +11 -2
- data/ext/ca_obj_tile.c +27 -7
- data/ext/ca_obj_transpose.c +8 -4
- data/ext/ca_obj_triop.c +3 -0
- data/ext/ca_obj_window.c +79 -50
- data/ext/ca_op_ipower.c +4 -4
- data/ext/ca_sort_kernels.h +6 -3
- data/ext/carray.h +33 -0
- data/ext/carray_access.c +16 -3
- data/ext/carray_attribute.c +24 -0
- data/ext/carray_cast.c +243 -82
- data/ext/carray_cast_func.rb +56 -26
- data/ext/carray_conversion.c +2 -2
- data/ext/carray_core.c +3 -0
- data/ext/carray_count.c +6 -0
- data/ext/carray_element.c +9 -4
- data/ext/carray_factorize.c +20 -20
- data/ext/carray_generate.c +8 -2
- data/ext/carray_histogram.c +1 -1
- data/ext/carray_index_classifier.c +4 -4
- data/ext/carray_internal.h +12 -0
- data/ext/carray_kernels_bincmp.c +168 -168
- data/ext/carray_kernels_binop.c +900 -900
- data/ext/carray_kernels_init.c +4 -4
- data/ext/carray_kernels_moncmp.c +96 -96
- data/ext/carray_kernels_monop.c +1984 -1984
- data/ext/carray_kernels_reduce_aggregate.c +1733 -1733
- data/ext/carray_kernels_reduce_cumulative.c +642 -642
- data/ext/carray_kernels_reduce_extreme.c +1150 -1150
- data/ext/carray_kernels_reduce_variance.c +258 -258
- data/ext/carray_kernels_scan.c +128 -128
- data/ext/carray_kernels_search.c +340 -340
- data/ext/carray_kernels_sort.c +295 -304
- data/ext/carray_kernels_triop.c +114 -114
- data/ext/carray_key_windows.c +4 -0
- data/ext/carray_mask.c +45 -22
- data/ext/carray_memory_view.c +2 -2
- data/ext/carray_operator.c +5 -5
- data/ext/carray_order.c +227 -8
- data/ext/carray_random.c +18 -18
- data/ext/carray_scatter.c +24 -7
- data/ext/carray_slab.c +32 -9
- data/ext/carray_sort.c +224 -16
- data/ext/carray_sort_kernel.c +5 -7
- data/ext/carray_struct.c +35 -43
- data/ext/carray_time_text.c +4 -2
- data/ext/carray_utils.c +63 -21
- data/ext/mkkernel.rb +210 -115
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +17 -4
- data/lib/carray/basics.rb +27 -7
- data/lib/carray/categorical.rb +30 -16
- data/lib/carray/categorical_iterator.rb +6 -6
- data/lib/carray/const_string.rb +10 -0
- data/lib/carray/data_type_extension.rb +8 -1
- data/lib/carray/fixlen_string.rb +41 -2
- data/lib/carray/frame/column_verbs.rb +4 -3
- data/lib/carray/frame/concat.rb +17 -3
- data/lib/carray/frame/frame.rb +6 -7
- data/lib/carray/frame/group.rb +100 -13
- data/lib/carray/frame/io.rb +3 -1
- data/lib/carray/fuse_source.rb +31 -2
- data/lib/carray/lazy.rb +35 -0
- data/lib/carray/methods/is_in.rb +8 -1
- data/lib/carray/segment_iterator.rb +12 -8
- data/lib/carray/serialize.rb +19 -2
- data/lib/carray/stack.rb +15 -12
- data/lib/carray/string.rb +8 -0
- data/lib/carray/string_operation_extension.rb +13 -5
- data/lib/carray/struct_builder.rb +5 -3
- data/lib/carray/time.rb +107 -68
- data/yard-stubs/ca_obj_transpose.rb +5 -4
- data/yard-stubs/carray_attribute.rb +3 -1
- data/yard-stubs/carray_cast.rb +2 -0
- data/yard-stubs/carray_conversion.rb +5 -1
- data/yard-stubs/carray_element.rb +7 -6
- data/yard-stubs/carray_mask.rb +8 -3
- data/yard-stubs/carray_math.rb +14 -4
- data/yard-stubs/carray_order.rb +100 -4
- data/yard-stubs/carray_scatter.rb +7 -5
- metadata +1 -1
data/ext/version.h
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#define CA_VERSION "3.0.
|
|
2
|
-
#define CA_VERSION_CODE
|
|
1
|
+
#define CA_VERSION "3.0.5"
|
|
2
|
+
#define CA_VERSION_CODE 30005
|
|
3
3
|
#define CA_VERSION_MAJOR 3
|
|
4
4
|
#define CA_VERSION_MINOR 0
|
|
5
|
-
#define CA_VERSION_TEENY
|
|
6
|
-
#define CA_VERSION_DATE "2026/10/
|
|
5
|
+
#define CA_VERSION_TEENY 5
|
|
6
|
+
#define CA_VERSION_DATE "2026/10/20"
|
data/lib/carray/attribute.rb
CHANGED
|
@@ -33,14 +33,17 @@
|
|
|
33
33
|
# parent's cells, so the parent's attributes do not describe them.
|
|
34
34
|
# `a.lazy` itself (CALazyMarker) is a view of `a` and walks through,
|
|
35
35
|
# and so does a lazy type conversion (the cast lane `promote_list`
|
|
36
|
-
# and `wrap_readonly` build), for the reason `to_type` carries them
|
|
36
|
+
# and `wrap_readonly` build), for the reason `to_type` carries them,
|
|
37
|
+
# and `swap_bytes`: like `refer`, `bitarray` and `bitfield`, a view
|
|
38
|
+
# that reads the same cells another way carries them.
|
|
37
39
|
# A view of a Face shows the attributes of the Face it was taken
|
|
38
40
|
# from (the walk goes to that Face, which the view's own parent
|
|
39
41
|
# chain skips).
|
|
40
42
|
# A view of several arrays (CAStack / CAMeld, so `stack`, `meld` and
|
|
41
43
|
# `montage`) shows, below its own @attr, the keys every one of its
|
|
42
44
|
# arrays shows with the same value; a key they disagree on, or one
|
|
43
|
-
# some of them lack, is not shown.
|
|
45
|
+
# some of them lack, is not shown. An array with no cells is left out
|
|
46
|
+
# of that count, unless all of them are empty.
|
|
44
47
|
#
|
|
45
48
|
# Stored values are frozen copies, nested Arrays and Hashes included, so
|
|
46
49
|
# an attribute changes only through set_attr / set_attrs: a value read
|
|
@@ -58,7 +61,9 @@
|
|
|
58
61
|
# `categorize`, a CAFrame `cast` / `parse_to_time` / `to_time`) carries
|
|
59
62
|
# them. Arithmetic and reductions do not -- the result is a different
|
|
60
63
|
# quantity -- including those that pick a value (`first`, `last`, `min`,
|
|
61
|
-
# `max`, `unique`)
|
|
64
|
+
# `max`, `unique`) and the element-wise ones that keep the unit (`abs`,
|
|
65
|
+
# `round`, `clip`): an attribute does not say which operations leave it
|
|
66
|
+
# true, so none carries it. Set it on the result again. A CAFrame index built from a key column's values
|
|
62
67
|
# (group and pivot) shows the key column's attributes.
|
|
63
68
|
|
|
64
69
|
class CArray
|
|
@@ -235,7 +240,12 @@ class CArray
|
|
|
235
240
|
end
|
|
236
241
|
|
|
237
242
|
# The attributes every array in `list` shows with the same value.
|
|
243
|
+
# An array with no cells holds no values for the attributes to describe,
|
|
244
|
+
# so it has no say (unless every one is empty), as CAFrame leaves a frame
|
|
245
|
+
# with no rows out of a meld.
|
|
238
246
|
def self.attrs_shared_by (list)
|
|
247
|
+
with_cells = list.reject { |a| a.elements == 0 }
|
|
248
|
+
list = with_cells unless with_cells.empty?
|
|
239
249
|
first, *rest = list.map(&:attrs)
|
|
240
250
|
return {} if first.nil?
|
|
241
251
|
first.select { |k, v| rest.all? { |h| h.key?(k) && h[k] == v } }
|
|
@@ -243,7 +253,10 @@ class CArray
|
|
|
243
253
|
private_class_method :attrs_shared_by
|
|
244
254
|
|
|
245
255
|
def attr_computing_view? (ca)
|
|
246
|
-
|
|
256
|
+
if ca.is_a?(CAMonOp)
|
|
257
|
+
op = ca.__op_id__
|
|
258
|
+
return op < CAMonOp::CAST_BASE && op != CAMonOp::OP_BYTE_SWAP
|
|
259
|
+
end
|
|
247
260
|
ca.is_a?(CABinOp) || ca.is_a?(CATriOp) ||
|
|
248
261
|
ca.is_a?(CAMonCmp) || ca.is_a?(CABinCmp)
|
|
249
262
|
end
|
data/lib/carray/basics.rb
CHANGED
|
@@ -117,13 +117,15 @@ class CArray
|
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
# @overload drop_axis
|
|
120
|
-
# Returns a view of `self` with every size-1 axis dropped.
|
|
120
|
+
# Returns a view of `self` with every size-1 axis dropped. When every
|
|
121
|
+
# axis has size 1, one axis of size 1 is kept.
|
|
121
122
|
# @return [CArray] view with reduced `ndim`.
|
|
122
123
|
def drop_axis
|
|
123
124
|
if ndim == 1
|
|
124
125
|
return self[]
|
|
125
126
|
else
|
|
126
127
|
newdim = shape.reject{|x| x == 1 }
|
|
128
|
+
newdim = [1] if newdim.empty? # every axis has size 1: keep one
|
|
127
129
|
return ( ndim != newdim.size ) ? reshape(*newdim) : self[]
|
|
128
130
|
end
|
|
129
131
|
end
|
|
@@ -189,7 +191,13 @@ class CArray
|
|
|
189
191
|
# @param src [CArray] source array.
|
|
190
192
|
# @return [self]
|
|
191
193
|
# @raise [ArgumentError] when `offset.length != self.ndim`.
|
|
194
|
+
# @raise [FrozenError] when `self` is frozen.
|
|
192
195
|
def paste (offset, src)
|
|
196
|
+
# The write goes through a window view, which is not frozen itself:
|
|
197
|
+
# refuse a frozen self here, as a write into it directly is refused.
|
|
198
|
+
if frozen?
|
|
199
|
+
raise FrozenError.new("can't modify frozen #{self.class}: #{inspect}", receiver: self)
|
|
200
|
+
end
|
|
193
201
|
raise ArgumentError, "offset length must equal ndim" if offset.length != ndim
|
|
194
202
|
# A zero-length source covers no cell, so there is nothing to write --
|
|
195
203
|
# and the empty range it would ask for is not a window CAWindow can
|
|
@@ -310,8 +318,7 @@ class CArray
|
|
|
310
318
|
# @overload clip(min, max = nil, fill_value = nil, lfill: nil, ufill: nil)
|
|
311
319
|
# Returns `self` with every element clamped to `[min, max]`.
|
|
312
320
|
#
|
|
313
|
-
# Either bound may be `nil` for a one-sided clip
|
|
314
|
-
# dispatches to the `pmax` / `pmin` binop kernels. When
|
|
321
|
+
# Either bound may be `nil` for a one-sided clip. When
|
|
315
322
|
# `fill_value` (or `lfill` / `ufill`) is given, out-of-range
|
|
316
323
|
# cells are replaced by the fill instead of clamped -- pass
|
|
317
324
|
# `UNDEF` to mask that end. `fill_value` is sugar for symmetric
|
|
@@ -320,6 +327,8 @@ class CArray
|
|
|
320
327
|
#
|
|
321
328
|
# `min` may not exceed `max` (in any cell, when a bound is an
|
|
322
329
|
# array); equal bounds give every unmasked cell that value.
|
|
330
|
+
# A NaN cell of `self` stays NaN, one-sided or two-sided (unlike
|
|
331
|
+
# `pmax` / `pmin`, which answer the other operand for it).
|
|
323
332
|
# A NaN in a bound array leaves that cell unbounded on that side,
|
|
324
333
|
# as `nil` does for the whole side. A bound is never UNDEF or
|
|
325
334
|
# masked: a missing bound is not read as either, so fill it first.
|
|
@@ -373,19 +382,30 @@ class CArray
|
|
|
373
382
|
|
|
374
383
|
if lfill.nil? && ufill.nil?
|
|
375
384
|
return clamp_between(min, max) if !min.nil? && !max.nil?
|
|
376
|
-
return
|
|
377
|
-
return
|
|
385
|
+
return clamp_below(min) if max.nil?
|
|
386
|
+
return clamp_above(max)
|
|
378
387
|
end
|
|
379
388
|
|
|
380
389
|
# A side without a fill still clamps.
|
|
381
390
|
out = self.copy
|
|
382
|
-
out = out.
|
|
383
|
-
out = out.
|
|
391
|
+
out = out.clamp_below(min) if !min.nil? && lfill.nil?
|
|
392
|
+
out = out.clamp_above(max) if !max.nil? && ufill.nil?
|
|
384
393
|
out[self.lt(min)] = lfill unless min.nil? || lfill.nil?
|
|
385
394
|
out[self.gt(max)] = ufill unless max.nil? || ufill.nil?
|
|
386
395
|
out
|
|
387
396
|
end
|
|
388
397
|
|
|
398
|
+
# A one-sided clip of a float array passes a NaN cell through, as the
|
|
399
|
+
# two-sided one does: the open side is a NaN bound, which leaves it
|
|
400
|
+
# unbounded. pmax / pmin would answer the bound for a NaN cell.
|
|
401
|
+
protected def clamp_below (min)
|
|
402
|
+
float? ? clamp_between(min, Float::NAN) : pmax(min)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
protected def clamp_above (max)
|
|
406
|
+
float? ? clamp_between(Float::NAN, max) : pmin(max)
|
|
407
|
+
end
|
|
408
|
+
|
|
389
409
|
# `contains` was retired in favour of {#is_in} (value-hash membership).
|
|
390
410
|
# `a.contains(v1, v2)` -> `a.is_in([v1, v2])`;
|
|
391
411
|
# `a.contains(v1, v2, axis: k)` -> `a.is_in([v1, v2]).any(axis: k)`.
|
data/lib/carray/categorical.rb
CHANGED
|
@@ -299,7 +299,7 @@ class CACategorical < CAObject
|
|
|
299
299
|
# `codes[i] = x` raises; use `codes.copy` for a mutable copy.
|
|
300
300
|
# @return [CArray]
|
|
301
301
|
def codes
|
|
302
|
-
|
|
302
|
+
face_storage
|
|
303
303
|
end
|
|
304
304
|
|
|
305
305
|
# Face hook: carry the vocabulary across lifted views (slice / reshape / …).
|
|
@@ -328,10 +328,17 @@ class CACategorical < CAObject
|
|
|
328
328
|
# Face hook: decode a per-cell code into its category label, through the one
|
|
329
329
|
# decode every code-to-label path shares (see #label_at).
|
|
330
330
|
def storage_to_element(raw)
|
|
331
|
-
code = raw.is_a?(String) ? raw.unpack1(UNPACK_FORMAT.fetch(
|
|
331
|
+
code = raw.is_a?(String) ? raw.unpack1(UNPACK_FORMAT.fetch(face_storage.data_type)) : raw
|
|
332
332
|
label_at(code)
|
|
333
333
|
end
|
|
334
334
|
|
|
335
|
+
# Face hook: a value written into a cell (a fill_value of shift and
|
|
336
|
+
# window) is a label, and the cell holds its code.
|
|
337
|
+
# @raise [ArgumentError] if the value is not one of the labels.
|
|
338
|
+
def element_to_storage (value)
|
|
339
|
+
code_for_fill(value)
|
|
340
|
+
end
|
|
341
|
+
|
|
335
342
|
# ---- category-space operations (by label, not code) -------------------
|
|
336
343
|
#
|
|
337
344
|
# The element value of a categorical IS its category, so these specialise
|
|
@@ -389,7 +396,14 @@ class CACategorical < CAObject
|
|
|
389
396
|
# (0 for an unknown label).
|
|
390
397
|
# @param label [Object] category to count.
|
|
391
398
|
# @return [Integer]
|
|
392
|
-
|
|
399
|
+
# @overload count
|
|
400
|
+
# Returns the number of cells in some category (not masked), as
|
|
401
|
+
# {CArray#count} with no value does.
|
|
402
|
+
# @return [Integer]
|
|
403
|
+
def count(*args)
|
|
404
|
+
return count_not_masked if args.empty?
|
|
405
|
+
raise ArgumentError, "count: wrong number of arguments (given #{args.size}, expected 0..1)" if args.size > 1
|
|
406
|
+
label = args[0]
|
|
393
407
|
code = @labels.index(label)
|
|
394
408
|
code ? codes.count(code) : 0
|
|
395
409
|
end
|
|
@@ -400,7 +414,7 @@ class CACategorical < CAObject
|
|
|
400
414
|
#
|
|
401
415
|
# Memoised: the codes are read-only (immutable storage), so the counts are a
|
|
402
416
|
# pure function of the categorical and stay valid for its lifetime. The same
|
|
403
|
-
# cached array backs {#reduceat_index} and {#
|
|
417
|
+
# cached array backs {#reduceat_index} and {#group_addr}, so a wide aggregate
|
|
404
418
|
# over the same categorical pays the count once. (Do not mutate the returned
|
|
405
419
|
# array — it is shared; take `.copy` for a scratch buffer.) A derived-view /
|
|
406
420
|
# composite categorical caches too: read-only rides from the codes as a flag,
|
|
@@ -543,7 +557,7 @@ class CACategorical < CAObject
|
|
|
543
557
|
# The code a fill value stands for: UNDEF stays UNDEF, a label is its code.
|
|
544
558
|
def code_for_fill (value)
|
|
545
559
|
return UNDEF if UNDEF.equal?(value)
|
|
546
|
-
code =
|
|
560
|
+
code = label_position(value)
|
|
547
561
|
raise ArgumentError, "#{value.inspect} is not one of the labels #{labels.inspect}" unless code
|
|
548
562
|
code
|
|
549
563
|
end
|
|
@@ -583,27 +597,27 @@ class CACategorical < CAObject
|
|
|
583
597
|
# two accessors are the building blocks for that other engine — lay the data
|
|
584
598
|
# out as category-contiguous blocks, then select per block.
|
|
585
599
|
#
|
|
586
|
-
# The grouping plan —
|
|
600
|
+
# The grouping plan — group_addr (the counting sort, the dominant cost) plus
|
|
587
601
|
# reduceat_index and category_sizes — depends only on the codes, which are
|
|
588
602
|
# read-only. It is therefore memoised (a pure function of the categorical): the
|
|
589
603
|
# first grouping access builds it and every later one reuses it. This is what
|
|
590
604
|
# lets a wide aggregate (many payload columns over one classifier) or a
|
|
591
605
|
# CACategoricalIterator run the counting sort once, not once per column /
|
|
592
606
|
# iterator. Only the plan is cached; the payload-dependent grouped copy
|
|
593
|
-
# (value.reshape(n)[
|
|
607
|
+
# (value.reshape(n)[group_addr]) is rebuilt per column (it varies with value).
|
|
594
608
|
|
|
595
|
-
# Force-build (and cache) the whole grouping plan up front —
|
|
609
|
+
# Force-build (and cache) the whole grouping plan up front — group_addr,
|
|
596
610
|
# reduceat_index, category_sizes — for prepare-ahead use before a batch of
|
|
597
611
|
# groupings. Lazy building already covers correctness; this is the explicit
|
|
598
612
|
# "pay the counting sort now" handle (e.g. right after `df.group_by(col)`).
|
|
599
613
|
# Returns self so it chains.
|
|
600
614
|
# @overload build_grouping
|
|
601
|
-
# Eagerly builds and caches the grouping plan (
|
|
615
|
+
# Eagerly builds and caches the grouping plan (group_addr / reduceat_index
|
|
602
616
|
# / category_sizes). Optional — the plan is built lazily on first grouping
|
|
603
617
|
# access — but useful to pay the counting sort once ahead of a batch.
|
|
604
618
|
# @return [self]
|
|
605
619
|
def build_grouping
|
|
606
|
-
|
|
620
|
+
group_addr # pulls category_sizes; reduceat_index shares category_sizes
|
|
607
621
|
reduceat_index
|
|
608
622
|
self
|
|
609
623
|
end
|
|
@@ -617,26 +631,26 @@ class CACategorical < CAObject
|
|
|
617
631
|
# order. The first {#category_sizes}.sum addresses are exactly the classified
|
|
618
632
|
# cells in category order — CACategoricalIterator's permutation. For an N-D
|
|
619
633
|
# categorical the addresses are into the raveled storage, so
|
|
620
|
-
# `value.reshape(elements)[cat.
|
|
634
|
+
# `value.reshape(elements)[cat.group_addr]` produces the contiguous blocks.
|
|
621
635
|
# Memoised (see the grouping-plan note above): the counting sort is the
|
|
622
636
|
# dominant grouping cost, so it is computed once and shared by every payload
|
|
623
637
|
# column and by CACategoricalIterator, and kept for the categorical's
|
|
624
638
|
# lifetime (an int64 per cell). Do not mutate the returned array.
|
|
625
|
-
# @overload
|
|
639
|
+
# @overload group_addr
|
|
626
640
|
# Returns a length-{#elements} integer CArray of flat storage
|
|
627
641
|
# addresses that order the cells by category (excluded cells last).
|
|
628
642
|
# @return [CArray]
|
|
629
|
-
def
|
|
630
|
-
return @
|
|
643
|
+
def group_addr
|
|
644
|
+
return @group_addr if @group_addr
|
|
631
645
|
out = codes.reshape(elements).__send__(:addresses_by_category,
|
|
632
646
|
reduceat_index.copy, @labels.size,
|
|
633
647
|
category_sizes.sum)
|
|
634
|
-
@
|
|
648
|
+
@group_addr = out
|
|
635
649
|
out
|
|
636
650
|
end
|
|
637
651
|
|
|
638
652
|
# Segment start offsets into the category-contiguous layout produced by
|
|
639
|
-
# {#
|
|
653
|
+
# {#group_addr}: reduceat_index[c] is where category c's block begins, so
|
|
640
654
|
# block c spans `reduceat_index[c] ... reduceat_index[c] + category_sizes[c]`.
|
|
641
655
|
# Empty categories repeat the following start (a zero-width block). Length k,
|
|
642
656
|
# aligned to #labels; pair with {#category_sizes} for the block lengths. On
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# grp.median # order statistics share the same surface
|
|
16
16
|
# grp.stddev
|
|
17
17
|
#
|
|
18
|
-
# This is the consumer of the
|
|
18
|
+
# This is the consumer of the group_addr / reduceat_index foundation on
|
|
19
19
|
# CACategorical. Order statistics (median / percentile / quantile) cannot be
|
|
20
20
|
# scattered — they need every value of a group held together — so the values
|
|
21
21
|
# are materialized into contiguous blocks once, and every reduction (monoid or
|
|
@@ -82,7 +82,7 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
82
82
|
# once and shared by every payload column and iterator — see
|
|
83
83
|
# CACategorical's grouping-plan note). The plan gives the segment STARTS
|
|
84
84
|
# (reduceat_index) and the group-major permutation (perm[slot] = source index
|
|
85
|
-
# at that grouped slot, = the valid prefix of
|
|
85
|
+
# at that grouped slot, = the valid prefix of group_addr); gathering value
|
|
86
86
|
# through perm is the only per-column work. Excluded cells (masked or
|
|
87
87
|
# out-of-vocabulary code) are absent from perm, so they never join a group;
|
|
88
88
|
# the value mask rides the gather into the grouped copy.
|
|
@@ -109,7 +109,7 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
109
109
|
@offsets = cat.reduceat_index # cached segment STARTS (int64[k])
|
|
110
110
|
@bounds = CArray.segment_offsets(lengths: @elements) # every boundary (int64[k+1])
|
|
111
111
|
# The group-major source indices (#perm) are taken from the cached
|
|
112
|
-
#
|
|
112
|
+
# group_addr when first asked for.
|
|
113
113
|
@nvalid = nvalid
|
|
114
114
|
@codes = cat.codes.reshape(cat.elements) # flat codes view (map re-walk / weights)
|
|
115
115
|
# Gather value into category-contiguous order via the cached permutation
|
|
@@ -316,12 +316,12 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
316
316
|
|
|
317
317
|
# Both are built with the grouping plan, and only on the flat path; the
|
|
318
318
|
# per-fiber form has neither and must not build a segment's.
|
|
319
|
-
# Group-major source indices = the valid prefix of the cached
|
|
319
|
+
# Group-major source indices = the valid prefix of the cached group_addr.
|
|
320
320
|
# With no classified cell the prefix is empty (and slicing a length-0
|
|
321
|
-
#
|
|
321
|
+
# group_addr would be out of range), so take the empty permutation directly.
|
|
322
322
|
def perm
|
|
323
323
|
if @nvalid && ! @perm
|
|
324
|
-
@perm = @nvalid > 0 ? @cat.
|
|
324
|
+
@perm = @nvalid > 0 ? @cat.group_addr[0...@nvalid] : CArray.int64(0)
|
|
325
325
|
end
|
|
326
326
|
@perm
|
|
327
327
|
end
|
data/lib/carray/const_string.rb
CHANGED
|
@@ -92,6 +92,16 @@ class CAConstString
|
|
|
92
92
|
# override the L1 generics where present.
|
|
93
93
|
include CArray::StringOperationMixin
|
|
94
94
|
|
|
95
|
+
# Face hook: a value written into a cell (a fill_value of shift and
|
|
96
|
+
# window). A cell is a byte range of the shared buffer, which has no room
|
|
97
|
+
# for a String it does not hold, so only UNDEF can be written.
|
|
98
|
+
# @raise [ArgumentError] for any value but UNDEF.
|
|
99
|
+
def element_to_storage (value)
|
|
100
|
+
raise ArgumentError,
|
|
101
|
+
"a CAConstString can't hold a new String (its strings share one " \
|
|
102
|
+
"buffer); use UNDEF, or to_string first"
|
|
103
|
+
end
|
|
104
|
+
|
|
95
105
|
# ---- ordering family ---------------------------------------------------
|
|
96
106
|
#
|
|
97
107
|
# A storage cell is a `(start, end)` byte range into the shared buffer, so
|
|
@@ -164,8 +164,15 @@ class CArray
|
|
|
164
164
|
# midpoints go the same way as numpy's integer linspace).
|
|
165
165
|
float_out = CArray.new(CA_FLOAT64, [n]).span(x1.to_f..x2.to_f)
|
|
166
166
|
return float_out if data_type == CA_FLOAT64
|
|
167
|
+
# to_type masks a cell the type cannot hold; linspace refuses it
|
|
168
|
+
# instead, as arange does.
|
|
167
169
|
# coerce: the type the caller asked for
|
|
168
|
-
float_out.floor.to_type(data_type)
|
|
170
|
+
out = float_out.floor.to_type(data_type)
|
|
171
|
+
if out.any_masked?
|
|
172
|
+
raise RangeError,
|
|
173
|
+
"linspace: #{x1.inspect}..#{x2.inspect} is out of range for #{out.data_type_name}"
|
|
174
|
+
end
|
|
175
|
+
out
|
|
169
176
|
end
|
|
170
177
|
|
|
171
178
|
# @overload arange(stop)
|
data/lib/carray/fixlen_string.rb
CHANGED
|
@@ -32,9 +32,11 @@ class CArray
|
|
|
32
32
|
# zero-copy, other string-bearing arrays materialise, numeric is rejected.
|
|
33
33
|
# @overload fixlen_string(values, bytes: nil, truncate: :error)
|
|
34
34
|
# @param values [Array<String, nil>] source values.
|
|
35
|
-
# @param bytes [Integer, nil] slot width; defaults to the
|
|
35
|
+
# @param bytes [Integer, nil] slot width, 1 or more; defaults to the
|
|
36
|
+
# max bytesize (1 when every string is empty).
|
|
36
37
|
# @param truncate [Symbol] `:error` or `:silent`.
|
|
37
38
|
# @return [CAFixlenString]
|
|
39
|
+
# @raise [ArgumentError] when `bytes` is given below 1.
|
|
38
40
|
# @overload fixlen_string(ca, bytes: nil, truncate: :error)
|
|
39
41
|
# @param ca [CArray] a String Face, CA_OBJECT, or raw CA_FIXLEN array.
|
|
40
42
|
# @return [CAFixlenString]
|
|
@@ -63,8 +65,11 @@ class CArray
|
|
|
63
65
|
values = arg.to_a
|
|
64
66
|
end
|
|
65
67
|
|
|
68
|
+
unless bytes.nil? || (bytes.is_a?(Integer) && bytes >= 1)
|
|
69
|
+
raise ArgumentError, "CArray.fixlen_string: invalid bytes: #{bytes.inspect} (a cell needs bytes: of 1 or more)"
|
|
70
|
+
end
|
|
66
71
|
width = bytes || values.compact.map { |s| s.to_s.bytesize }.max || 1
|
|
67
|
-
width = 1 if width < 1
|
|
72
|
+
width = 1 if width < 1 # every string empty: the narrowest cell
|
|
68
73
|
|
|
69
74
|
if truncate == :error
|
|
70
75
|
values.each_with_index do |s, i|
|
|
@@ -98,4 +103,38 @@ class CAFixlenString
|
|
|
98
103
|
|
|
99
104
|
include CArray::StringOperationMixin
|
|
100
105
|
include CArray::StringOperationMixin::Mutable
|
|
106
|
+
|
|
107
|
+
# ---- order family beyond the fixlen kernels --------------------------
|
|
108
|
+
#
|
|
109
|
+
# min / max / sort run on the cells' bytes. The members those kernels do
|
|
110
|
+
# not cover answer here: minmax as the pair of them, and the running
|
|
111
|
+
# extremes through the strings ({#to_string}), as CAString answers them,
|
|
112
|
+
# written back at this width.
|
|
113
|
+
|
|
114
|
+
# @return [Array(String, String), Array(CAFixlenString, CAFixlenString)]
|
|
115
|
+
# `[min, max]`, with the keywords of {CArray#min} / {CArray#max}.
|
|
116
|
+
def minmax (*args, **kw)
|
|
117
|
+
[min(*args, **kw), max(*args, **kw)]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @return [CAFixlenString] the running maximum, as {CArray#cummax}.
|
|
121
|
+
def cummax (*args, **kw)
|
|
122
|
+
to_string.cummax(*args, **kw).to_fixlen_string(bytes: bytes)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @return [CAFixlenString] the running minimum, as {CArray#cummin}.
|
|
126
|
+
def cummin (*args, **kw)
|
|
127
|
+
to_string.cummin(*args, **kw).to_fixlen_string(bytes: bytes)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# A String has no distance, so there is no nearest one; refused in the
|
|
131
|
+
# words CAString uses.
|
|
132
|
+
def search_nearest (*args, **kw)
|
|
133
|
+
to_string.search_nearest(*args, **kw)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @see #search_nearest
|
|
137
|
+
def search_nearest_addr (*args, **kw)
|
|
138
|
+
to_string.search_nearest_addr(*args, **kw)
|
|
139
|
+
end
|
|
101
140
|
end
|
|
@@ -30,18 +30,19 @@ class CAFrame
|
|
|
30
30
|
next if key_names.include?(name)
|
|
31
31
|
col = @frame[name]
|
|
32
32
|
next unless column_has?(col, verb, args, keywords)
|
|
33
|
-
cols[name] = yield(col)
|
|
33
|
+
cols[name] = yield(col, name)
|
|
34
34
|
end
|
|
35
35
|
return cols
|
|
36
36
|
end
|
|
37
37
|
results = names.map do |name|
|
|
38
|
-
|
|
38
|
+
key = @frame.__send__(:column_key, name, verb)
|
|
39
|
+
col = @frame[key]
|
|
39
40
|
unless column_has?(col, verb, args, keywords)
|
|
40
41
|
raise CArray::DataTypeError,
|
|
41
42
|
"#{verb}: column #{name.inspect} " \
|
|
42
43
|
"(#{CArray::Inspector.type_label(col)}) has no #{verb}"
|
|
43
44
|
end
|
|
44
|
-
yield(col)
|
|
45
|
+
yield(col, key)
|
|
45
46
|
end
|
|
46
47
|
results.size == 1 ? results.first : results
|
|
47
48
|
end
|
data/lib/carray/frame/concat.rb
CHANGED
|
@@ -20,9 +20,9 @@ class CAFrame
|
|
|
20
20
|
# preserved: writes to the result flow back to whichever input frame owns
|
|
21
21
|
# the target segment, and vice versa).
|
|
22
22
|
#
|
|
23
|
-
# Per-column +data_type+ must match across frames
|
|
24
|
-
#
|
|
25
|
-
#
|
|
23
|
+
# Per-column +data_type+ must match across frames: CArray.meld would bring
|
|
24
|
+
# the pieces to a common type, and in a frame that promotion would hide
|
|
25
|
+
# schema drift. For an eager, auto-casting alternative use
|
|
26
26
|
# {CAFrame.concatenate}.
|
|
27
27
|
#
|
|
28
28
|
# Column matching is by name (output order follows the first frame);
|
|
@@ -54,6 +54,7 @@ class CAFrame
|
|
|
54
54
|
frames = frames_with_rows(frames)
|
|
55
55
|
cols = {}
|
|
56
56
|
names.each do |name|
|
|
57
|
+
check_same_column_type(frames.map { |f| f[name] }, name, verb: "meld")
|
|
57
58
|
cols[name] = CArray.meld(frames.map { |f| f[name] }, axis: 0)
|
|
58
59
|
end
|
|
59
60
|
new(cols, axis_name: first.axis_name, index: meld_index(frames))
|
|
@@ -229,6 +230,19 @@ class CAFrame
|
|
|
229
230
|
end
|
|
230
231
|
private_class_method :check_trailing_shapes
|
|
231
232
|
|
|
233
|
+
# The pieces of one column have one data type (and one class, for a
|
|
234
|
+
# Face): a frame does not promote a column to make its pieces agree.
|
|
235
|
+
def self.check_same_column_type(pieces, name, verb:)
|
|
236
|
+
first = pieces.first
|
|
237
|
+
pieces.each_with_index do |c, i|
|
|
238
|
+
next if CArray::Inspector.type_label(c) == CArray::Inspector.type_label(first)
|
|
239
|
+
raise ArgumentError,
|
|
240
|
+
"#{verb}: column #{name.inspect} is #{CArray::Inspector.type_label(c)} in piece #{i}, " \
|
|
241
|
+
"#{CArray::Inspector.type_label(first)} in piece 0 (cast it first, or use concatenate)"
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
private_class_method :check_same_column_type
|
|
245
|
+
|
|
232
246
|
# Two indexes hold the same rows when their values agree cell by cell; a
|
|
233
247
|
# NaN label matches a NaN label, and a masked cell a masked cell.
|
|
234
248
|
def self.same_index_values?(a, b)
|
data/lib/carray/frame/frame.rb
CHANGED
|
@@ -840,13 +840,12 @@ class CAFrame
|
|
|
840
840
|
case pieces.size
|
|
841
841
|
when 0 then col[CArray.int32(0), *tail] # replaced every row with none
|
|
842
842
|
when 1 then pieces.first
|
|
843
|
-
else
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
# hide schema drift.
|
|
843
|
+
else
|
|
844
|
+
# A splice does not promote the column (it would hide schema
|
|
845
|
+
# drift); cast the incoming column beforehand.
|
|
846
|
+
CAFrame.send(:check_same_column_type, pieces, name, verb: "splice")
|
|
847
|
+
CArray.meld(pieces, axis: 0)
|
|
848
|
+
end
|
|
850
849
|
splice_keep_attrs(new_cols[name], col)
|
|
851
850
|
end
|
|
852
851
|
new_index = splice_index(other, lo, hi)
|