carray 3.0.5 → 3.0.6
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 +233 -0
- data/README.md +1 -1
- data/carray.gemspec +3 -2
- data/ext/ca_axis_dispatch.c +6 -0
- data/ext/ca_axis_group.c +1 -1
- data/ext/ca_categorical_iterator.c +7 -7
- data/ext/ca_kernel_iterator.c +116 -231
- data/ext/ca_kernel_iterator.h +410 -254
- data/ext/ca_obj_bincmp.c +9 -1
- data/ext/ca_obj_binop.c +10 -1
- data/ext/ca_obj_bitfield.c +2 -0
- data/ext/ca_obj_byte_swap.c +2 -0
- data/ext/ca_obj_face.c +2 -0
- data/ext/ca_obj_fake.c +3 -1
- data/ext/ca_obj_grid.c +167 -12
- data/ext/ca_obj_meld.c +69 -113
- data/ext/ca_obj_moncmp.c +9 -1
- data/ext/ca_obj_monop.c +40 -2
- data/ext/ca_obj_object.c +54 -20
- data/ext/ca_obj_reduce.c +15 -1
- data/ext/ca_obj_refer.c +65 -20
- data/ext/ca_obj_remap.c +69 -10
- data/ext/ca_obj_roll.c +1 -0
- data/ext/ca_obj_select.c +4 -1
- data/ext/ca_obj_select_axis.c +1 -0
- data/ext/ca_obj_shift.c +1 -0
- data/ext/ca_obj_stack.c +310 -111
- data/ext/ca_obj_stride.c +131 -45
- data/ext/ca_obj_string.c +18 -2
- data/ext/ca_obj_tile.c +1 -0
- data/ext/ca_obj_triop.c +9 -1
- data/ext/ca_obj_window.c +1 -0
- data/ext/ca_op_ipower.c +8 -8
- data/ext/caframe_csv_reader.c +1 -1
- data/ext/carray.h +2 -1
- data/ext/carray_access.c +27 -64
- data/ext/carray_address_basis.c +134 -0
- data/ext/carray_attribute.c +8 -0
- data/ext/carray_broadcast.c +3 -4
- data/ext/carray_build_flags.h +1 -1
- data/ext/carray_cast.c +8 -11
- data/ext/carray_conversion.c +9 -5
- data/ext/carray_core.c +811 -72
- data/ext/carray_element.c +13 -9
- data/ext/carray_histogram.c +25 -3
- data/ext/carray_internal.h +147 -12
- data/ext/carray_kernels_bincmp.c +2007 -3
- data/ext/carray_kernels_binop.c +25365 -5199
- data/ext/carray_kernels_init.c +8 -8
- data/ext/carray_kernels_monop.c +10696 -3678
- data/ext/carray_kernels_reduce_aggregate.c +4429 -702
- data/ext/carray_kernels_reduce_boolean.c +1533 -15
- data/ext/carray_kernels_reduce_cumulative.c +1808 -248
- data/ext/carray_kernels_reduce_extreme.c +4508 -511
- data/ext/carray_kernels_reduce_variance.c +2284 -152
- data/ext/carray_kernels_scan.c +240 -240
- data/ext/carray_kernels_search.c +4211 -264
- data/ext/carray_kernels_sort.c +367 -450
- data/ext/carray_lazy.c +47 -0
- data/ext/carray_loop.c +1 -1
- data/ext/carray_mask.c +130 -139
- data/ext/carray_median_percentile.c +4 -4
- data/ext/carray_memory_view.c +13 -7
- data/ext/carray_operator.c +147 -44
- data/ext/carray_order.c +3 -2
- data/ext/carray_random.c +4 -1
- data/ext/carray_slab.c +97 -21
- data/ext/carray_sort.c +1 -1
- data/ext/carray_test.c +74 -19
- data/ext/carray_time_text.c +2 -2
- data/ext/extconf.rb +11 -0
- data/ext/mkkernel.rb +346 -94
- data/ext/ruby_carray.c +4 -1
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +25 -1
- data/lib/carray/autoload_carray.rb +3 -3
- data/lib/carray/autoload_method_extension.rb +5 -3
- data/lib/carray/axis_group.rb +13 -7
- data/lib/carray/basics.rb +1 -1
- data/lib/carray/block_iterator.rb +17 -3
- data/lib/carray/categorical.rb +6 -8
- data/lib/carray/categorical_iterator.rb +2 -2
- data/lib/carray/complex.rb +2 -2
- data/lib/carray/conditional.rb +3 -2
- data/lib/carray/frame/column_verbs.rb +45 -14
- data/lib/carray/frame/convert.rb +19 -0
- data/lib/carray/frame/csv_parser.rb +32 -32
- data/lib/carray/frame/describe.rb +4 -2
- data/lib/carray/frame/frame.rb +399 -78
- data/lib/carray/frame/group.rb +679 -53
- data/lib/carray/frame/io.rb +209 -71
- data/lib/carray/frame/join.rb +146 -37
- data/lib/carray/frame/records.rb +3 -3
- data/lib/carray/frame/reshape.rb +89 -11
- data/lib/carray/frame/sort.rb +57 -9
- data/lib/carray/frame/verbs.rb +384 -49
- data/lib/carray/frame/windows.rb +44 -6
- data/lib/carray/inspect.rb +7 -2
- data/lib/carray/iterator.rb +32 -0
- data/lib/carray/lazy.rb +17 -8
- data/lib/carray/mask_gap_fill.rb +37 -17
- data/lib/carray/meld_reduce.rb +1 -1
- data/lib/carray/methods/choose.rb +3 -2
- data/lib/carray/methods/meshgrid.rb +3 -1
- data/lib/carray/runtime.rb +47 -8
- data/lib/carray/segment_iterator.rb +23 -8
- data/lib/carray/serialize.rb +34 -17
- data/lib/carray/stack.rb +18 -6
- data/lib/carray/time.rb +5 -0
- data/lib/carray/window_iterator.rb +16 -4
- data/yard-stubs/ca_obj_array.rb +26 -1
- data/yard-stubs/carray_cast.rb +8 -5
- data/yard-stubs/carray_conversion.rb +8 -2
- data/yard-stubs/carray_mask.rb +20 -1
- data/yard-stubs/carray_math.rb +31 -16
- data/yard-stubs/carray_memory_view.rb +7 -0
- data/yard-stubs/carray_test.rb +7 -4
- metadata +18 -4
data/ext/ruby_carray.c
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
---------------------------------------------------------------------------- */
|
|
15
15
|
|
|
16
16
|
#include "carray.h"
|
|
17
|
+
#include "carray_internal.h"
|
|
17
18
|
#include "carray_build_flags.h"
|
|
18
19
|
#include "version.h"
|
|
19
20
|
|
|
@@ -313,7 +314,7 @@ Init_carray_ext (void)
|
|
|
313
314
|
Init_carray_sort(); /* sort / sort_copy / sort_addr / axis2addr */
|
|
314
315
|
Init_carray_count();
|
|
315
316
|
Init_carray_kernels(); /* mkkernel-generated: reductions + maps */
|
|
316
|
-
Init_carray_round(); /* round(ndigits)
|
|
317
|
+
Init_carray_round(); /* round(ndigits) / round!(ndigits) */
|
|
317
318
|
|
|
318
319
|
Init_carray_utils();
|
|
319
320
|
|
|
@@ -394,5 +395,7 @@ Init_carray_ext (void)
|
|
|
394
395
|
Init_carray_slab();
|
|
395
396
|
|
|
396
397
|
Init_carray_address_basis();
|
|
398
|
+
|
|
399
|
+
ca_obj_num_builtin = ca_obj_num;
|
|
397
400
|
}
|
|
398
401
|
|
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.6"
|
|
2
|
+
#define CA_VERSION_CODE 30006
|
|
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 6
|
|
6
|
+
#define CA_VERSION_DATE "2026/10/28"
|
data/lib/carray/attribute.rb
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# ca.set_attr(:unit, "m/s") # => setter; writes to self's @attr
|
|
7
7
|
# ca.set_attrs(other.attrs) # => sets every key of a Hash at once
|
|
8
8
|
# ca.attrs # => frozen shallow clone of the full Hash
|
|
9
|
+
# ca.attrs_symkey # => the same, with Symbol keys (nested too)
|
|
9
10
|
# ca.has_attr? # => any attribute present?
|
|
10
11
|
# ca.has_attr?(:unit) # => specific key present?
|
|
11
12
|
#
|
|
@@ -162,6 +163,15 @@ class CArray
|
|
|
162
163
|
(merged || {}).freeze
|
|
163
164
|
end
|
|
164
165
|
|
|
166
|
+
# @overload attrs_symkey
|
|
167
|
+
# Returns {#attrs} with its keys as Symbols, frozen. The keys of
|
|
168
|
+
# Hashes nested in the values, inside Arrays too, become Symbols
|
|
169
|
+
# as well.
|
|
170
|
+
# @return [Hash{Symbol => Object}]
|
|
171
|
+
def attrs_symkey
|
|
172
|
+
return CArray.__send__(:attr_symbolize_keys, self.attrs)
|
|
173
|
+
end
|
|
174
|
+
|
|
165
175
|
# @overload has_attr?
|
|
166
176
|
# Returns whether `self` (or any parent view) has any attribute
|
|
167
177
|
# set.
|
|
@@ -317,6 +327,20 @@ class CArray
|
|
|
317
327
|
v
|
|
318
328
|
end
|
|
319
329
|
end
|
|
320
|
-
|
|
330
|
+
# A stored value with the keys of every Hash in it as Symbols, frozen
|
|
331
|
+
# as the stored value is.
|
|
332
|
+
def self.attr_symbolize_keys (v)
|
|
333
|
+
case v
|
|
334
|
+
when Array then v.map { |e| attr_symbolize_keys(e) }.freeze
|
|
335
|
+
when Hash
|
|
336
|
+
v.each_with_object({}) do |(k, vv), h|
|
|
337
|
+
h[k.to_sym] = attr_symbolize_keys(vv)
|
|
338
|
+
end.freeze
|
|
339
|
+
else
|
|
340
|
+
v
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
private_class_method :attr_normalize_key, :attr_validate_value, :attr_coerce_value,
|
|
344
|
+
:attr_symbolize_keys
|
|
321
345
|
|
|
322
346
|
end
|
|
@@ -32,12 +32,12 @@ class CArray
|
|
|
32
32
|
autoload_method "marshal_load", serialize_rb
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# A view, a wrap and a scalar are marshalled through _dump / self._load,
|
|
35
|
+
# A view, a mask, a wrap and a scalar are marshalled through _dump / self._load,
|
|
36
36
|
# which return a new array: with marshal_dump Marshal records the class and
|
|
37
37
|
# loads into an empty instance of it, which the plain array the payload
|
|
38
38
|
# holds cannot become. A record array keeps marshal_dump, under which it
|
|
39
39
|
# already round-trips with its class.
|
|
40
|
-
[CAView, CAWrap, CScalar].each do |klass|
|
|
40
|
+
[CAView, CAWrap, CScalar, CArrayMask, CAObjectMask].each do |klass|
|
|
41
41
|
klass.class_eval do
|
|
42
42
|
undef_method :marshal_dump
|
|
43
43
|
autoload_method "_dump", "carray/serialize"
|
|
@@ -137,7 +137,7 @@ autoload :CAUnion, "carray/struct"
|
|
|
137
137
|
# ---- CAFrame (DataFrame over CArray columns) -------------------------------
|
|
138
138
|
#
|
|
139
139
|
# The frame is only reached through the CAFrame constant (CAFrame.new /
|
|
140
|
-
# .
|
|
140
|
+
# .read_csv / subclassing) or GroupedFrame (obtained from CAFrame#group_by,
|
|
141
141
|
# by which point CAFrame is already loaded). Both constants live in the same
|
|
142
142
|
# require bundle (carray/frame.rb), so autoloading either loads the whole
|
|
143
143
|
# frame. Nothing in the eager core references them, so it loads on first use.
|
|
@@ -31,9 +31,11 @@ module AutoloadMethodExtension
|
|
|
31
31
|
|
|
32
32
|
private
|
|
33
33
|
|
|
34
|
-
def autoload_define (target, name,
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
def autoload_define (target, name, library_given, spec_given)
|
|
35
|
+
# The stub reads these two, so they are never assigned again: Ruby 4.0
|
|
36
|
+
# refuses to make a Proc shareable when a variable it reads may be.
|
|
37
|
+
library = Ractor.make_shareable(library_given.to_s.dup)
|
|
38
|
+
original_spec = Ractor.make_shareable(spec_given.to_s.dup)
|
|
37
39
|
# Shareable, so the stub answers in any Ractor. Only the main Ractor
|
|
38
40
|
# loads the library: Ractors that require at the same time can deadlock
|
|
39
41
|
# (Ruby 3.4), so another Ractor is told what to load there instead.
|
data/lib/carray/axis_group.rb
CHANGED
|
@@ -409,7 +409,7 @@ class CAGroupIterator
|
|
|
409
409
|
# except for a band folded into the count, which has no route but the
|
|
410
410
|
# engine.
|
|
411
411
|
def core_counts? (kw)
|
|
412
|
-
return false if CArray.__fast_paths_enabled__
|
|
412
|
+
return false if CArray.__fast_paths_enabled__(:axis_group_count_kernel)
|
|
413
413
|
has_group, fused = AxisGroup.parse_axis(kw[:axis])
|
|
414
414
|
has_group && fused.empty?
|
|
415
415
|
end
|
|
@@ -494,8 +494,8 @@ class CAGroupIterator
|
|
|
494
494
|
# Weighted mean = Sum(v*w) / Sum(w), both over the combined present-set. The
|
|
495
495
|
# `value*0 + weights` denominator carries the same value|weight mask, so its
|
|
496
496
|
# group-sum is Sum(w) over exactly the cells value*weight used. An empty group
|
|
497
|
-
# (no present pair)
|
|
498
|
-
#
|
|
497
|
+
# (no present pair) and a group whose weights sum to zero are UNDEF, as
|
|
498
|
+
# CArray#wmean leaves both.
|
|
499
499
|
def wmean (weights, **kw)
|
|
500
500
|
has_group, = AxisGroup.parse_axis(kw[:axis])
|
|
501
501
|
return value.wmean(weights, **kw) unless has_group
|
|
@@ -508,7 +508,7 @@ class CAGroupIterator
|
|
|
508
508
|
# No present pair is UNDEF (an empty group), set on the denominator so
|
|
509
509
|
# the division never meets an empty group's 0 / 0.
|
|
510
510
|
den = den.copy
|
|
511
|
-
den[cnt.eq(0)] = UNDEF
|
|
511
|
+
den[cnt.eq(0).or(den.eq(0))] = UNDEF
|
|
512
512
|
# min_count: counts the present pairs, as the core wmean does.
|
|
513
513
|
pairs = regroup(prod, spec)
|
|
514
514
|
pairs.finish_reduction(:wmean, kw, num / den)
|
|
@@ -863,7 +863,7 @@ class CAGroupIterator
|
|
|
863
863
|
# pass. With the fast paths off it goes through the band loop instead,
|
|
864
864
|
# which then has one position.
|
|
865
865
|
def whole_block? (bslots)
|
|
866
|
-
bslots.empty? && CArray.__fast_paths_enabled__
|
|
866
|
+
bslots.empty? && CArray.__fast_paths_enabled__(:axis_group_whole_block)
|
|
867
867
|
end
|
|
868
868
|
|
|
869
869
|
# Iterate the band positions of a band-preserving grouping. For each band
|
|
@@ -923,9 +923,15 @@ class CAGroupIterator
|
|
|
923
923
|
# paths off (a development build run with CARRAY_GENERAL_PATH set) each
|
|
924
924
|
# group is reduced by the core instead -- except a band folded into the
|
|
925
925
|
# statistic (+fused+), which has no route but the kernel.
|
|
926
|
+
#
|
|
927
|
+
# With no band axis the core groups the whole value in one pass, which is
|
|
928
|
+
# faster than the kernel for every group size (1M cells on an M2 Max: 7.5
|
|
929
|
+
# to 12.5 ms against 15 ms), so the kernel is taken only with a band.
|
|
926
930
|
def kernel_reads_value? (fused = [])
|
|
927
931
|
return false if value.face? || ! (value.integer? || value.float?)
|
|
928
|
-
|
|
932
|
+
return true unless fused.empty?
|
|
933
|
+
return false if spec.slot_meta.none? { |m| m[:kind] == :band }
|
|
934
|
+
CArray.__fast_paths_enabled__(:axis_group_reduce_kernel)
|
|
929
935
|
end
|
|
930
936
|
|
|
931
937
|
# Whether the scan kernel gives the core's answer: it reads an integer,
|
|
@@ -933,7 +939,7 @@ class CAGroupIterator
|
|
|
933
939
|
# float64 (an object in object), cummax / cummin in the payload's data type
|
|
934
940
|
# and cumcount in int64 -- not the core's answer for a boolean's running sum.
|
|
935
941
|
def kernel_scans? (op)
|
|
936
|
-
return false unless CArray.__fast_paths_enabled__
|
|
942
|
+
return false unless CArray.__fast_paths_enabled__(:axis_group_scan_kernel)
|
|
937
943
|
return false if value.face? || value.complex?
|
|
938
944
|
return true if value.object? || op == :cumcount
|
|
939
945
|
core = CAIterator.core_probe(value).public_send(op).data_type
|
data/lib/carray/basics.rb
CHANGED
|
@@ -122,7 +122,7 @@ class CArray
|
|
|
122
122
|
# @return [CArray] view with reduced `ndim`.
|
|
123
123
|
def drop_axis
|
|
124
124
|
if ndim == 1
|
|
125
|
-
return self[]
|
|
125
|
+
return scalar? ? self : self[]
|
|
126
126
|
else
|
|
127
127
|
newdim = shape.reject{|x| x == 1 }
|
|
128
128
|
newdim = [1] if newdim.empty? # every axis has size 1: keep one
|
|
@@ -74,6 +74,14 @@ class CABlockIterator < CAIterator
|
|
|
74
74
|
"blocks: expected #{source.ndim} tile sizes (one per axis), " \
|
|
75
75
|
"got #{blocks.size}"
|
|
76
76
|
end
|
|
77
|
+
# Each tile is read through a view with one axis per tile position and
|
|
78
|
+
# one per cell in the tile, twice the source's rank.
|
|
79
|
+
if 2 * source.ndim > CA_RANK_MAX
|
|
80
|
+
raise ArgumentError,
|
|
81
|
+
"blocks: a rank #{source.ndim} array would need #{2 * source.ndim} " \
|
|
82
|
+
"axes for its tiles, beyond the limit of #{CA_RANK_MAX}; " \
|
|
83
|
+
"blocks works up to rank #{CA_RANK_MAX / 2}"
|
|
84
|
+
end
|
|
77
85
|
|
|
78
86
|
@sndim = source.ndim
|
|
79
87
|
offsets = Array.new(@sndim, 0)
|
|
@@ -93,7 +101,9 @@ class CABlockIterator < CAIterator
|
|
|
93
101
|
# Absorb a leading offset with a zero-copy pre-slice, so the tile geometry
|
|
94
102
|
# below always starts at index 0.
|
|
95
103
|
@source =
|
|
96
|
-
if
|
|
104
|
+
if source.scalar?
|
|
105
|
+
source.flatten # a CScalar takes no index; its one-cell view does
|
|
106
|
+
elsif offsets.all?(&:zero?)
|
|
97
107
|
source
|
|
98
108
|
else
|
|
99
109
|
source[*offsets.map { |o| o..-1 }]
|
|
@@ -517,7 +527,7 @@ class CABlockIterator < CAIterator
|
|
|
517
527
|
sview = saddr[*strip_ranges].block_view(*tiles)
|
|
518
528
|
grid = (0...@sndim).map { |i| vview.shape[i] }
|
|
519
529
|
cells = tiles.inject(1) { |p, t| p * t }
|
|
520
|
-
order = vview.copy.reshape(*(grid + [cells])).
|
|
530
|
+
order = vview.copy.reshape(*(grid + [cells])).sort_index(axis: @sndim)
|
|
521
531
|
src_sorted = sview.reshape(*(grid + [cells])).take_along_axis(order, axis: @sndim)
|
|
522
532
|
out[*strip_ranges].block_view(*tiles)[] = src_sorted.reshape(*(grid + tiles))
|
|
523
533
|
end
|
|
@@ -817,7 +827,7 @@ class CABlockIterator < CAIterator
|
|
|
817
827
|
# A padded entity whose interior holds the source and whose partial-edge
|
|
818
828
|
# margin is masked (UNDEF), sized to exactly cover the ceil tile grid. When
|
|
819
829
|
# the source already divides evenly this is the source itself (no copy), so
|
|
820
|
-
# the tiles alias it.
|
|
830
|
+
# the tiles alias it. Kept for one call; feeds the iterate escape hatches.
|
|
821
831
|
def padded_source
|
|
822
832
|
@padded_source ||= begin
|
|
823
833
|
pshape = @sndim.times.map { |i| @shape[i] * @sizes[i] }
|
|
@@ -845,6 +855,8 @@ class CABlockIterator < CAIterator
|
|
|
845
855
|
def lag (*args, **keywords) = @source.__send__(:carry_attrs, super)
|
|
846
856
|
def strip_mask (**keywords) = @source.__send__(:carry_attrs, super)
|
|
847
857
|
end)
|
|
858
|
+
|
|
859
|
+
read_source_per_call :@padded_source, :@tile_grid_view
|
|
848
860
|
end
|
|
849
861
|
|
|
850
862
|
|
|
@@ -857,6 +869,8 @@ class CArray
|
|
|
857
869
|
# first for "valid" tiling.
|
|
858
870
|
# @param blocks [Array<Integer, Range>] per-axis tile sizes (or ranges).
|
|
859
871
|
# @return [CABlockIterator]
|
|
872
|
+
# @raise [ArgumentError] when `self` has a rank above `CA_RANK_MAX / 2`
|
|
873
|
+
# (8): the tiles are read through a view of twice the rank.
|
|
860
874
|
def blocks (*blocks)
|
|
861
875
|
CABlockIterator.new(self, *blocks)
|
|
862
876
|
end
|
data/lib/carray/categorical.rb
CHANGED
|
@@ -415,8 +415,8 @@ class CACategorical < CAObject
|
|
|
415
415
|
# Memoised: the codes are read-only (immutable storage), so the counts are a
|
|
416
416
|
# pure function of the categorical and stay valid for its lifetime. The same
|
|
417
417
|
# cached array backs {#reduceat_index} and {#group_addr}, so a wide aggregate
|
|
418
|
-
# over the same categorical pays the count once.
|
|
419
|
-
#
|
|
418
|
+
# over the same categorical pays the count once. The returned array is shared,
|
|
419
|
+
# so it is frozen; take `.copy` for a scratch buffer. A derived-view /
|
|
420
420
|
# composite categorical caches too: read-only rides from the codes as a flag,
|
|
421
421
|
# not a Ruby freeze, so the object stays non-frozen and the memo ivar sticks.
|
|
422
422
|
# @overload category_sizes
|
|
@@ -429,8 +429,7 @@ class CACategorical < CAObject
|
|
|
429
429
|
bc = codes.bincount
|
|
430
430
|
out = CArray.new(bc.data_type, [@labels.size], bytes: bc.bytes) # new zero-fills
|
|
431
431
|
out[0...bc.elements] = bc if bc.elements > 0
|
|
432
|
-
@category_sizes = out
|
|
433
|
-
out
|
|
432
|
+
@category_sizes = out.freeze
|
|
434
433
|
end
|
|
435
434
|
|
|
436
435
|
# Back-compatible alias; {#category_sizes} is the canonical name (a histogram
|
|
@@ -635,7 +634,7 @@ class CACategorical < CAObject
|
|
|
635
634
|
# Memoised (see the grouping-plan note above): the counting sort is the
|
|
636
635
|
# dominant grouping cost, so it is computed once and shared by every payload
|
|
637
636
|
# column and by CACategoricalIterator, and kept for the categorical's
|
|
638
|
-
# lifetime (an int64 per cell).
|
|
637
|
+
# lifetime (an int64 per cell). The returned array is shared, so it is frozen.
|
|
639
638
|
# @overload group_addr
|
|
640
639
|
# Returns a length-{#elements} integer CArray of flat storage
|
|
641
640
|
# addresses that order the cells by category (excluded cells last).
|
|
@@ -645,8 +644,7 @@ class CACategorical < CAObject
|
|
|
645
644
|
out = codes.reshape(elements).__send__(:addresses_by_category,
|
|
646
645
|
reduceat_index.copy, @labels.size,
|
|
647
646
|
category_sizes.sum)
|
|
648
|
-
@group_addr = out
|
|
649
|
-
out
|
|
647
|
+
@group_addr = out.freeze
|
|
650
648
|
end
|
|
651
649
|
|
|
652
650
|
# Segment start offsets into the category-contiguous layout produced by
|
|
@@ -663,7 +661,7 @@ class CACategorical < CAObject
|
|
|
663
661
|
return @reduceat_index if @reduceat_index
|
|
664
662
|
# Every boundary but the last; the copy makes it an entity the
|
|
665
663
|
# reduceat kernels can read directly.
|
|
666
|
-
@reduceat_index = CArray.segment_offsets(lengths: category_sizes)[0...-1].copy
|
|
664
|
+
@reduceat_index = CArray.segment_offsets(lengths: category_sizes)[0...-1].copy.freeze
|
|
667
665
|
end
|
|
668
666
|
|
|
669
667
|
# @overload categorize(labels: nil, sort_labels: false)
|
|
@@ -295,7 +295,7 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
295
295
|
# @overload wmean(weights, axis:)
|
|
296
296
|
# Per-fiber per-category weighted mean along `axis`. Same weights-shape
|
|
297
297
|
# contract as {#wsum} (weights.shape == source.shape). Empty cell → MASKED;
|
|
298
|
-
# a present cell whose weights sum to zero →
|
|
298
|
+
# a present cell whose weights sum to zero → UNDEF, as CArray#wmean.
|
|
299
299
|
# @param weights [CArray]
|
|
300
300
|
# @param axis [Integer]
|
|
301
301
|
# @return [CArray]
|
|
@@ -551,7 +551,7 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
551
551
|
codes = @cat.codes
|
|
552
552
|
# Classified along the axis, a group is taken by index; the general way,
|
|
553
553
|
# masking the rest of a copy, gives the same cells.
|
|
554
|
-
if codes.shape == [h.shape[axis]] && CArray.__fast_paths_enabled__
|
|
554
|
+
if codes.shape == [h.shape[axis]] && CArray.__fast_paths_enabled__(:categorical_group_by_index)
|
|
555
555
|
sel = none ? CArray.boolean(codes.elements) { false } : codes.eq(c)
|
|
556
556
|
sel = sel.strip_mask(false) if sel.has_mask?
|
|
557
557
|
idx = [nil] * h.ndim
|
data/lib/carray/complex.rb
CHANGED
|
@@ -74,7 +74,7 @@ class CArray
|
|
|
74
74
|
when CA_CMPLX128 then field(0, CA_FLOAT64)
|
|
75
75
|
end
|
|
76
76
|
else
|
|
77
|
-
self[]
|
|
77
|
+
scalar? ? self : self[]
|
|
78
78
|
end
|
|
79
79
|
frozen? ? view : (@__real__ = view)
|
|
80
80
|
end
|
|
@@ -100,7 +100,7 @@ class CArray
|
|
|
100
100
|
end
|
|
101
101
|
if not complex?
|
|
102
102
|
zero = template { 0 }
|
|
103
|
-
zero
|
|
103
|
+
zero.inherit_mask(self) if has_mask?
|
|
104
104
|
return zero
|
|
105
105
|
end
|
|
106
106
|
return @__imag__ if part_of_self?(@__imag__)
|
data/lib/carray/conditional.rb
CHANGED
|
@@ -219,8 +219,9 @@ class CArray
|
|
|
219
219
|
"conditional: cond must be a boolean CArray with same shape as self"
|
|
220
220
|
end
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
src = scalar? ? flatten : self # a CScalar takes no index
|
|
223
|
+
x_then = src[cond]
|
|
224
|
+
x_else = src[cond.not]
|
|
224
225
|
y_then = then_fn.call(x_then)
|
|
225
226
|
y_else = else_fn.call(x_else)
|
|
226
227
|
text = !data_type && CArray.send(:selection_text?, [y_then, y_else])
|
|
@@ -3,22 +3,32 @@
|
|
|
3
3
|
class CAFrame
|
|
4
4
|
|
|
5
5
|
# The one rule of a frame-wide verb over the frame's columns -- the group
|
|
6
|
-
# reductions and transforms, the windowed reductions
|
|
7
|
-
#
|
|
6
|
+
# reductions and transforms, the windowed reductions, and the frame's own
|
|
7
|
+
# reductions and running values -- kept in one place: which columns a verb
|
|
8
|
+
# computes, and what a named column without the operation does.
|
|
8
9
|
#
|
|
9
|
-
# - given names, the CArray (one) or
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
10
|
+
# - given names, those columns, as the CArray (one) or a Hash of name to
|
|
11
|
+
# result (several); a named column whose type has no such operation is
|
|
12
|
+
# refused with CArray::DataTypeError, as the core refuses it, since it
|
|
13
|
+
# was asked for;
|
|
13
14
|
# - given none, every column whose type has it, the key columns left out,
|
|
14
|
-
# as a Hash of name to result
|
|
15
|
+
# as a Hash of name to result.
|
|
16
|
+
#
|
|
17
|
+
# The caller decides the shape of the answer from these: a verb whose
|
|
18
|
+
# answers lie on the frame's rows (a transform, a window at the rows,
|
|
19
|
+
# +keep_rows:+) hands one named column back as it is, as +df[name]+
|
|
20
|
+
# returns it, and several as a frame on the frame's rows, while a verb
|
|
21
|
+
# with one row per group, per point or for the whole frame makes a frame
|
|
22
|
+
# of them, labelled by its rows.
|
|
15
23
|
#
|
|
16
24
|
# Whether a type has the operation is asked of the core on two copies of
|
|
17
25
|
# the column's first row (two blank cells of its kind when it has no
|
|
18
26
|
# rows), so the answer is about the type and not the data: a refusal
|
|
19
27
|
# there means the operation does not apply, while an error in the verb
|
|
20
|
-
# itself is left to raise. An
|
|
21
|
-
#
|
|
28
|
+
# itself is left to raise. An object column is the exception: its type
|
|
29
|
+
# says nothing of what it holds, so one with no present value is left out
|
|
30
|
+
# of a verb run over every column. An N-D column (each row a profile) is
|
|
31
|
+
# asked, and computed, along the rows.
|
|
22
32
|
module ColumnVerbs
|
|
23
33
|
|
|
24
34
|
private
|
|
@@ -29,13 +39,17 @@ class CAFrame
|
|
|
29
39
|
@frame.column_names.each do |name|
|
|
30
40
|
next if key_names.include?(name)
|
|
31
41
|
col = @frame[name]
|
|
42
|
+
next if valueless_object_column?(col, verb, args)
|
|
32
43
|
next unless column_has?(col, verb, args, keywords)
|
|
33
44
|
cols[name] = yield(col, name)
|
|
34
45
|
end
|
|
35
46
|
return cols
|
|
36
47
|
end
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
# A column named twice would be one result column twice over.
|
|
49
|
+
keys = CAFrame.__send__(:refuse_repeated_names,
|
|
50
|
+
names.map { |name| @frame.__send__(:column_key, name, verb) }, verb)
|
|
51
|
+
results = keys.map do |key|
|
|
52
|
+
name = key
|
|
39
53
|
col = @frame[key]
|
|
40
54
|
unless column_has?(col, verb, args, keywords)
|
|
41
55
|
raise CArray::DataTypeError,
|
|
@@ -44,7 +58,17 @@ class CAFrame
|
|
|
44
58
|
end
|
|
45
59
|
yield(col, key)
|
|
46
60
|
end
|
|
47
|
-
results.size == 1 ? results.first : results
|
|
61
|
+
results.size == 1 ? results.first : keys.zip(results).to_h
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# An object column with no present cell says nothing of what it holds,
|
|
65
|
+
# so a verb run over every column leaves it out, as select leaves it out
|
|
66
|
+
# of every kind; counting the present cells applies to any column.
|
|
67
|
+
def valueless_object_column? (col, verb, args)
|
|
68
|
+
return false if col.face? || col.data_type != CA_OBJECT
|
|
69
|
+
return false if %i[count_not_masked count_masked].include?(verb)
|
|
70
|
+
return false if verb == :count && args.empty?
|
|
71
|
+
col.count_not_masked == 0
|
|
48
72
|
end
|
|
49
73
|
|
|
50
74
|
# count with no value counts the present cells, which every type has;
|
|
@@ -54,10 +78,17 @@ class CAFrame
|
|
|
54
78
|
verb = :count_not_masked if verb == :count && args.empty?
|
|
55
79
|
# Two cells: one cell does not reach what combines two (a String's
|
|
56
80
|
# median interpolates, its sum adds), and an empty column answers
|
|
57
|
-
# with the identity without asking the type at all.
|
|
81
|
+
# with the identity without asking the type at all. The row is the
|
|
82
|
+
# first that holds a present cell, so a masked first row does not
|
|
83
|
+
# answer with the identity for a column whose values have none.
|
|
58
84
|
tail = [nil] * (col.ndim - 1)
|
|
85
|
+
row = 0
|
|
86
|
+
if col.has_mask? && col.elements > 0
|
|
87
|
+
present = col.is_not_masked.flatten.where
|
|
88
|
+
row = present[0] / (col.elements / col.shape[0]) if present.elements > 0
|
|
89
|
+
end
|
|
59
90
|
probe = if col.shape[0] > 0
|
|
60
|
-
col[CA_INT64([
|
|
91
|
+
col[CA_INT64([row, row]), *tail]
|
|
61
92
|
else
|
|
62
93
|
col.resize(2, *col.shape[1..])
|
|
63
94
|
end
|
data/lib/carray/frame/convert.rb
CHANGED
|
@@ -26,6 +26,25 @@ class CAFrame
|
|
|
26
26
|
each_row.map { |row| row.transform_values { |v| record_value(v) } }
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# The rows as an Array of Arrays, one value per column in column order --
|
|
30
|
+
# the table as plain Ruby data, for a library that takes rows (anything
|
|
31
|
+
# that calls +to_a+ on what it is given). A cell is what the column's own
|
|
32
|
+
# +to_a+ gives: a masked cell is +UNDEF+, a time column's cell its element,
|
|
33
|
+
# and an N-D column's cell an Array. The index is a row label, not a value,
|
|
34
|
+
# and is left out; +reset_index+ first to have it as the first column.
|
|
35
|
+
#
|
|
36
|
+
# Masked cells are not turned into anything: decide what they should be
|
|
37
|
+
# before handing the rows over, with +strip_mask+ or +drop_masked+.
|
|
38
|
+
#
|
|
39
|
+
# df.drop_masked.to_a
|
|
40
|
+
# df.strip_mask("temp" => Float::NAN).to_a
|
|
41
|
+
#
|
|
42
|
+
# @return [Array<Array>]
|
|
43
|
+
def to_a
|
|
44
|
+
return Array.new(@nrow) { [] } if @columns.empty?
|
|
45
|
+
return @columns.values.map(&:to_a).transpose
|
|
46
|
+
end
|
|
47
|
+
|
|
29
48
|
# Hand the frame over as a CArray with the minimum work (memo §11.9): a
|
|
30
49
|
# 2-D **view** of shape (nrow, ncol), one per column in column
|
|
31
50
|
# order (§12-C). Nothing is materialised — the result is a +CAStack+ over
|