carray 3.0.2 → 3.0.4
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 +568 -0
- data/README.md +6 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +220 -0
- data/ext/ca_axis_group.c +201 -123
- data/ext/ca_binop_dispatch.c +46 -52
- data/ext/ca_binop_dispatch.h +1 -1
- data/ext/ca_categorical_iterator.c +249 -183
- data/ext/ca_compare.c +7 -12
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +38 -23
- data/ext/ca_kernel_iterator.c +1765 -2076
- data/ext/ca_kernel_iterator.h +584 -566
- data/ext/ca_monop_dispatch.c +86 -63
- data/ext/ca_monop_dispatch.h +5 -0
- data/ext/ca_obj_array.c +223 -56
- data/ext/ca_obj_bincmp.c +172 -138
- data/ext/ca_obj_binop.c +116 -230
- data/ext/ca_obj_bitarray.c +40 -26
- data/ext/ca_obj_bitfield.c +100 -68
- data/ext/ca_obj_block.c +19 -9
- data/ext/ca_obj_byte_swap.c +45 -46
- data/ext/ca_obj_const_string.c +269 -44
- data/ext/ca_obj_face.c +612 -89
- data/ext/ca_obj_face.h +111 -47
- data/ext/ca_obj_fake.c +36 -79
- data/ext/ca_obj_farray.c +7 -3
- data/ext/ca_obj_field.c +33 -8
- data/ext/ca_obj_fixlen_string.c +23 -24
- data/ext/ca_obj_grid.c +541 -210
- data/ext/ca_obj_meld.c +331 -111
- data/ext/ca_obj_moncmp.c +80 -65
- data/ext/ca_obj_monop.c +177 -149
- data/ext/ca_obj_object.c +664 -248
- data/ext/ca_obj_record.c +63 -23
- data/ext/ca_obj_reduce.c +18 -16
- data/ext/ca_obj_refer.c +140 -58
- data/ext/ca_obj_remap.c +85 -24
- data/ext/ca_obj_repeat.c +24 -9
- data/ext/ca_obj_roll.c +121 -16
- data/ext/ca_obj_select.c +61 -58
- data/ext/ca_obj_select_axis.c +73 -107
- data/ext/ca_obj_shift.c +32 -102
- data/ext/ca_obj_stack.c +372 -122
- data/ext/ca_obj_stride.c +348 -140
- data/ext/ca_obj_string.c +88 -22
- data/ext/ca_obj_tile.c +38 -21
- data/ext/ca_obj_time.c +45 -31
- data/ext/ca_obj_timedelta.c +43 -29
- data/ext/ca_obj_transpose.c +17 -6
- data/ext/ca_obj_triop.c +125 -141
- data/ext/ca_obj_window.c +330 -44
- data/ext/ca_op_ipower.c +24 -11
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +104 -81
- data/ext/ca_sweep_engine.h +7 -20
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +542 -0
- data/ext/carray.h +241 -92
- data/ext/carray_access.c +608 -276
- data/ext/carray_address_basis.c +494 -97
- data/ext/carray_attribute.c +129 -4
- data/ext/carray_bincount.c +39 -7
- data/ext/carray_broadcast.c +56 -19
- data/ext/carray_build_flags.h +1 -1
- data/ext/carray_call_cfunc.c +108 -28
- data/ext/carray_cast.c +1576 -223
- data/ext/carray_cast_func.rb +77 -24
- data/ext/carray_class.c +4 -3
- data/ext/carray_conversion.c +208 -75
- data/ext/carray_copy.c +82 -24
- data/ext/carray_core.c +646 -162
- data/ext/carray_count.c +171 -88
- data/ext/carray_element.c +94 -60
- data/ext/carray_factorize.c +670 -324
- data/ext/carray_generate.c +260 -83
- data/ext/carray_histogram.c +209 -106
- data/ext/carray_hold.c +14 -16
- data/ext/carray_index_classifier.c +422 -243
- data/ext/carray_internal.h +692 -3
- data/ext/carray_kernels_bincmp.c +7 -0
- data/ext/carray_kernels_binop.c +487 -120
- data/ext/carray_kernels_init.c +653 -50
- data/ext/carray_kernels_moncmp.c +15 -8
- data/ext/carray_kernels_monop.c +99 -43
- data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
- data/ext/carray_kernels_reduce_boolean.c +112 -36
- data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
- data/ext/carray_kernels_reduce_extreme.c +3032 -2892
- data/ext/carray_kernels_reduce_variance.c +439 -500
- data/ext/carray_kernels_scan.c +1097 -488
- data/ext/carray_kernels_search.c +3787 -2176
- data/ext/carray_kernels_sort.c +1200 -920
- data/ext/carray_kernels_triop.c +479 -7
- data/ext/carray_key_windows.c +216 -0
- data/ext/carray_lazy.c +967 -153
- data/ext/carray_loop.c +81 -65
- data/ext/carray_mask.c +910 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +299 -100
- data/ext/carray_memory_view.c +872 -758
- data/ext/carray_operator.c +1171 -364
- data/ext/carray_order.c +505 -133
- data/ext/carray_partition.c +112 -65
- data/ext/carray_random.c +327 -239
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +346 -78
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +249 -86
- data/ext/carray_sort.c +224 -227
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +118 -39
- data/ext/carray_test.c +193 -69
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +579 -113
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +19 -4
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1202 -1014
- data/ext/ruby_carray.c +17 -5
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +160 -17
- data/lib/carray/autoload_carray.rb +23 -27
- data/lib/carray/autoload_method_extension.rb +20 -5
- data/lib/carray/axis_group.rb +360 -63
- data/lib/carray/basics.rb +162 -28
- data/lib/carray/bincount_nd.rb +18 -2
- data/lib/carray/block_iterator.rb +231 -58
- data/lib/carray/boolean_reduce.rb +34 -42
- data/lib/carray/categorical.rb +242 -52
- data/lib/carray/categorical_iterator.rb +437 -827
- data/lib/carray/complex.rb +41 -34
- data/lib/carray/conditional.rb +150 -14
- data/lib/carray/const_string.rb +59 -4
- data/lib/carray/construct.rb +15 -11
- data/lib/carray/data_type_extension.rb +1 -0
- data/lib/carray/distance_limit.rb +77 -0
- data/lib/carray/fixlen_string.rb +7 -1
- data/lib/carray/frame/column_verbs.rb +70 -0
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +82 -7
- data/lib/carray/frame/csv_parser.rb +688 -87
- data/lib/carray/frame/describe.rb +80 -0
- data/lib/carray/frame/frame.rb +369 -45
- data/lib/carray/frame/group.rb +631 -90
- data/lib/carray/frame/io.rb +506 -79
- data/lib/carray/frame/join.rb +411 -62
- data/lib/carray/frame/records.rb +116 -19
- data/lib/carray/frame/reshape.rb +455 -0
- data/lib/carray/frame/sort.rb +30 -18
- data/lib/carray/frame/verbs.rb +868 -61
- data/lib/carray/frame/windows.rb +117 -0
- data/lib/carray/frame.rb +4 -0
- data/lib/carray/fuse_source.rb +118 -2
- data/lib/carray/fusion.rb +257 -42
- data/lib/carray/histogram.rb +6 -1
- data/lib/carray/inspect.rb +54 -13
- data/lib/carray/iterator.rb +99 -9
- data/lib/carray/key_windows.rb +620 -0
- data/lib/carray/lazy.rb +216 -193
- data/lib/carray/mask_gap_fill.rb +112 -92
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +52 -21
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bin.rb +26 -18
- data/lib/carray/methods/bincount.rb +18 -2
- data/lib/carray/methods/bit_string.rb +15 -4
- data/lib/carray/methods/choose.rb +16 -3
- data/lib/carray/methods/composition.rb +18 -10
- data/lib/carray/methods/factorize.rb +2 -2
- data/lib/carray/methods/gather_nd.rb +25 -5
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_between.rb +59 -0
- data/lib/carray/methods/is_in.rb +140 -10
- data/lib/carray/methods/join.rb +2 -7
- data/lib/carray/methods/lag.rb +49 -0
- data/lib/carray/methods/locate_addr.rb +182 -45
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +11 -6
- data/lib/carray/methods/repeat.rb +1 -20
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +11 -2
- data/lib/carray/methods/value_counts.rb +4 -11
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +3 -0
- data/lib/carray/runtime.rb +65 -17
- data/lib/carray/segment_iterator.rb +1221 -0
- data/lib/carray/serialize.rb +214 -30
- data/lib/carray/slab_iterator.rb +101 -27
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string.rb +105 -5
- data/lib/carray/string_operation_extension.rb +12 -4
- data/lib/carray/struct.rb +137 -58
- data/lib/carray/struct_builder.rb +124 -17
- data/lib/carray/time.rb +1222 -186
- data/lib/carray/window_iterator.rb +242 -81
- data/lib/carray.rb +9 -2
- data/yard-stubs/ca_obj_array.rb +4 -4
- data/yard-stubs/ca_obj_bitarray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +23 -12
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +5 -1
- data/yard-stubs/ca_obj_grid.rb +21 -10
- data/yard-stubs/ca_obj_refer.rb +5 -4
- data/yard-stubs/ca_obj_roll.rb +2 -3
- data/yard-stubs/ca_obj_shift.rb +5 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_tile.rb +1 -1
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +9 -5
- data/yard-stubs/carray_access.rb +11 -5
- data/yard-stubs/carray_attribute.rb +0 -5
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_cast.rb +88 -43
- data/yard-stubs/carray_conversion.rb +15 -2
- data/yard-stubs/carray_copy.rb +12 -5
- data/yard-stubs/carray_core.rb +3 -3
- data/yard-stubs/carray_count.rb +9 -0
- data/yard-stubs/carray_element.rb +10 -4
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_lazy.rb +5 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +60 -12
- data/yard-stubs/carray_math.rb +52 -14
- data/yard-stubs/carray_median_percentile.rb +15 -8
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +61 -29
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +21 -9
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +10 -2
- data/yard-stubs/carray_sort.rb +49 -8
- data/yard-stubs/carray_test.rb +15 -3
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -5
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_kernels_map.c +0 -3467
- data/lib/carray/arrow_tensor.rb +0 -401
- data/lib/carray/table.rb +0 -108
data/lib/carray/frame/join.rb
CHANGED
|
@@ -6,40 +6,70 @@
|
|
|
6
6
|
# +project+ (length-preserving, miss -> UNDEF, Face-lift, read-only safe).
|
|
7
7
|
# The frame layer just distributes that address to every column.
|
|
8
8
|
|
|
9
|
+
require "carray/methods/is_in" # promote_value_set, for align's Array reference
|
|
10
|
+
require "carray/methods/locate_addr" # nearest_refuse_bytes, for join_asof
|
|
11
|
+
|
|
9
12
|
class CAFrame
|
|
10
13
|
# Default disambiguation for non-key columns present on both sides (§12-C):
|
|
11
14
|
# left gets "_left", right gets "_right". +_left+/+_right+ read clearer than
|
|
12
15
|
# pandas' +_x+/+_y+ for the target audience.
|
|
13
16
|
DEFAULT_JOIN_SUFFIXES = ["_left", "_right"].freeze
|
|
17
|
+
Ractor.make_shareable(DEFAULT_JOIN_SUFFIXES)
|
|
14
18
|
private_constant :DEFAULT_JOIN_SUFFIXES
|
|
15
19
|
|
|
16
|
-
# Join +other+ on a shared key column
|
|
20
|
+
# Join +other+ on a shared key column, as SQL joins: each pair of a left
|
|
21
|
+
# row and a right row with the same key gives one row, so a key on k left
|
|
22
|
+
# rows and m right rows gives k * m rows. A masked key matches nothing.
|
|
23
|
+
#
|
|
24
|
+
# how: :left (default) — every pair, and each left row with no match
|
|
25
|
+
# once, its right columns UNDEF.
|
|
26
|
+
# how: :inner — every pair.
|
|
27
|
+
# how: :right — every pair, and each right row with no match once.
|
|
28
|
+
# how: :outer — every pair, each unmatched left row, then each unmatched
|
|
29
|
+
# right row.
|
|
17
30
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# the +on+ column.
|
|
31
|
+
# Rows come in left-row order (right-row order for :right), the pairs of
|
|
32
|
+
# one row in the other side's order. The left columns are views of this
|
|
33
|
+
# frame's, picked by row, wherever every row comes from the left (:left,
|
|
34
|
+
# :inner); the right columns are gathered into new arrays.
|
|
23
35
|
#
|
|
24
36
|
# A non-key column present on both sides collides. The key column (+on+) is
|
|
25
37
|
# kept once, never suffixed. By default the collision is resolved by suffixing
|
|
26
38
|
# both sides (+suffixes: ["_left", "_right"]+); pass a 2-element array to pick
|
|
27
39
|
# meaningful names up front (+["_obs", "_fcst"]+), or +suffixes: false+ to
|
|
28
40
|
# raise instead. Rename afterward with +rename+ if needed (§12-C).
|
|
41
|
+
#
|
|
42
|
+
# +on:+ may name several columns, as an Array: rows then match when every
|
|
43
|
+
# key column matches, and a row with any key cell masked matches nothing.
|
|
44
|
+
#
|
|
45
|
+
# obs.join(fcst, on: ["station", "date"])
|
|
46
|
+
#
|
|
47
|
+
# Two modes filter this frame's rows by +other+ instead of pairing them:
|
|
48
|
+
#
|
|
49
|
+
# how: :semi — the rows whose key some row of +other+ has.
|
|
50
|
+
# how: :anti — the rows whose key no row of +other+ has.
|
|
51
|
+
#
|
|
52
|
+
# Each row appears at most once, in this frame's order, and no column of
|
|
53
|
+
# +other+ is added, so no name collides and +suffixes:+ is refused. A row
|
|
54
|
+
# with a masked key matches nothing: :semi drops it and :anti keeps it.
|
|
55
|
+
# The result shares this frame's columns and index, as +filter+'s does.
|
|
56
|
+
#
|
|
57
|
+
# obs.join(stations, on: "station", how: :anti) # stations not listed
|
|
29
58
|
def join(other, on:, how: :left, suffixes: DEFAULT_JOIN_SUFFIXES)
|
|
30
|
-
|
|
31
|
-
lkey = self[on]
|
|
32
|
-
rkey = other[on]
|
|
33
|
-
plan = join_name_plan(other, on, suffixes)
|
|
34
|
-
|
|
35
|
-
case how
|
|
36
|
-
when :left
|
|
37
|
-
join_left(other, on, lkey, rkey, plan)
|
|
38
|
-
when :inner, :outer, :right
|
|
39
|
-
join_align(other, on, lkey, rkey, how, plan)
|
|
40
|
-
else
|
|
59
|
+
unless %i[left inner outer right semi anti].include?(how)
|
|
41
60
|
raise ArgumentError, "unknown join mode #{how.inspect}"
|
|
42
61
|
end
|
|
62
|
+
if (how == :semi || how == :anti) && !suffixes.equal?(DEFAULT_JOIN_SUFFIXES)
|
|
63
|
+
raise ArgumentError, "join: how: #{how.inspect} adds no column of the other frame, so suffixes: does not apply"
|
|
64
|
+
end
|
|
65
|
+
names = join_key_names(on, "join", "on:")
|
|
66
|
+
lkeys = names.map { |name| key_column(name, "join") }
|
|
67
|
+
rkeys = names.map { |name| other.__send__(:key_column, name, "join") }
|
|
68
|
+
join_check_key_widths(names, lkeys, rkeys, "join")
|
|
69
|
+
return join_filter(lkeys, rkeys, how) if how == :semi || how == :anti
|
|
70
|
+
|
|
71
|
+
plan = join_name_plan(other, names, suffixes)
|
|
72
|
+
join_pairs(other, names, lkeys, rkeys, how, plan)
|
|
43
73
|
end
|
|
44
74
|
|
|
45
75
|
# As-of (nearest-key) left join for irregular series (memo §11.7). Each
|
|
@@ -47,21 +77,47 @@ class CAFrame
|
|
|
47
77
|
# locate_nearest_addr (the per-row, row-preserving counterpart of
|
|
48
78
|
# align_nearest_addr); +direction:+ follows CArray (:floor = most recent
|
|
49
79
|
# at-or-before, :ceil = next, :round = nearest), and rows with no match
|
|
50
|
-
# in
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
80
|
+
# in that direction or beyond +tolerance:+ come back UNDEF. +tolerance:+
|
|
81
|
+
# is a number for a numeric key and a duration for a time key
|
|
82
|
+
# ("10 minutes", or a CATimedelta value). Same wiring as the left join,
|
|
83
|
+
# only the address primitive differs.
|
|
84
|
+
#
|
|
85
|
+
# +by:+ names columns that must match exactly, one name or an Array: each
|
|
86
|
+
# left row is matched to the nearest row among the rows of +other+ with
|
|
87
|
+
# the same +by+ values. A row with a masked +by+ cell matches nothing.
|
|
88
|
+
#
|
|
89
|
+
# obs.join_asof(radar, on: "time", by: "station", tolerance: "10 minutes")
|
|
90
|
+
def join_asof(other, on:, by: nil, direction: :floor, tolerance: nil, suffixes: DEFAULT_JOIN_SUFFIXES)
|
|
91
|
+
on = column_key(on, "join_asof")
|
|
92
|
+
exact = by.nil? ? [] : join_key_names(by, "join_asof", "by:")
|
|
93
|
+
if exact.include?(on)
|
|
94
|
+
raise ArgumentError, "join_asof: #{on.inspect} is the on: key; by: names the others"
|
|
95
|
+
end
|
|
96
|
+
plan = join_name_plan(other, [on, *exact], suffixes)
|
|
97
|
+
lkey = key_column(on, "join_asof")
|
|
98
|
+
rkey = other.__send__(:key_column, on, "join_asof")
|
|
99
|
+
[lkey, rkey].each do |key|
|
|
100
|
+
key.__send__(:nearest_refuse_bytes, key, "join_asof: the on: key #{on.inspect}",
|
|
101
|
+
"join on it exactly with join")
|
|
102
|
+
end
|
|
103
|
+
addr = if exact.empty?
|
|
104
|
+
lkey.locate_nearest_addr(rkey, direction: direction, tolerance: tolerance)
|
|
105
|
+
else
|
|
106
|
+
lexact = exact.map { |name| key_column(name, "join_asof") }
|
|
107
|
+
rexact = exact.map { |name| other.__send__(:key_column, name, "join_asof") }
|
|
108
|
+
join_check_key_widths(exact, lexact, rexact, "join_asof")
|
|
109
|
+
_, lcode, rcode, k = join_codes(lexact, rexact)
|
|
110
|
+
join_asof_by(lkey, rkey, lcode, rcode, k, direction, tolerance)
|
|
111
|
+
end
|
|
112
|
+
join_by_addr(other, [on, *exact], addr, plan)
|
|
57
113
|
end
|
|
58
114
|
|
|
59
|
-
# Conform every
|
|
115
|
+
# Conform every column to an externally supplied reference key set (the
|
|
60
116
|
# asymmetric sibling of +join+; pandas +reindex+). Given a +reference+ array
|
|
61
117
|
# of key values, each column is gathered onto it by exact key match
|
|
62
|
-
# (+locate_addr+): the aligned key column (or index)
|
|
63
|
-
#
|
|
64
|
-
# every other column.
|
|
118
|
+
# (+locate_addr+): the aligned key column (or index) holds a copy of the
|
|
119
|
+
# reference, with the key's attributes, and a reference key absent from
|
|
120
|
+
# the source comes back UNDEF in every other column.
|
|
65
121
|
#
|
|
66
122
|
# CAFrame measures no interval and generates nothing -- the caller owns the
|
|
67
123
|
# reference. This is the primitive for reindexing to a caller-built axis
|
|
@@ -71,17 +127,31 @@ class CAFrame
|
|
|
71
127
|
# df.align("time", reftime) # reftime = a CArray of reference key values
|
|
72
128
|
#
|
|
73
129
|
# +key+ may be a column name or the index axis name. +reference+ is a CArray
|
|
74
|
-
#
|
|
75
|
-
#
|
|
130
|
+
# or an Array; its keys are matched against the source key by value. An
|
|
131
|
+
# Array is read as values of the key's kind, as +is_in+ reads one: numbers
|
|
132
|
+
# against a numeric key, Time / Date / String instants against a time key.
|
|
76
133
|
def align(key, reference)
|
|
77
|
-
key = key
|
|
78
|
-
ref = reference.is_a?(CArray) ? reference : reference.to_ca
|
|
134
|
+
key = column_key(key, "align")
|
|
79
135
|
on_index = !@columns.key?(key) && @axis_name == key && @index
|
|
80
|
-
|
|
136
|
+
keys = key_column(key, "align")
|
|
137
|
+
# An Array reference is read as values of the key's kind, as is_in reads
|
|
138
|
+
# one: 1 and 2.5 against a float key are numbers, and Time or String
|
|
139
|
+
# values against a time key are instants.
|
|
140
|
+
ref = reference.is_a?(CArray) ? reference : keys.send(:promote_value_set, reference)[1]
|
|
141
|
+
unless ref.ndim == 1
|
|
142
|
+
raise ArgumentError,
|
|
143
|
+
"align: the reference has shape #{ref.shape.inspect}; give one key value per row of the result"
|
|
144
|
+
end
|
|
145
|
+
addr = ref.locate_addr(keys)
|
|
146
|
+
# The result's keys are the reference's values, taken as they are now
|
|
147
|
+
# and shown with the key's attributes; the caller's array is left as
|
|
148
|
+
# it is.
|
|
149
|
+
ref = CAFrame.__send__(:key_labels, keys, reference.is_a?(CArray) ? ref.copy : ref)
|
|
81
150
|
|
|
82
151
|
cols = {}
|
|
83
|
-
|
|
152
|
+
column_names.each { |name| cols[name] = project_rows(self[name], addr) }
|
|
84
153
|
if on_index
|
|
154
|
+
# key-labels: ref was given the key's attributes above
|
|
85
155
|
CAFrame.new(cols, axis_name: key, index: ref)
|
|
86
156
|
else
|
|
87
157
|
cols[key] = ref
|
|
@@ -90,7 +160,7 @@ class CAFrame
|
|
|
90
160
|
end
|
|
91
161
|
end
|
|
92
162
|
|
|
93
|
-
# Paste +other+'s
|
|
163
|
+
# Paste +other+'s columns beside this frame's, matched by **row position**
|
|
94
164
|
# (memo §12-C) — a keyless column merge (the column-direction counterpart of
|
|
95
165
|
# the row-stacking +concat+, and the positional counterpart of the key-aligned
|
|
96
166
|
# +join+; named after the UNIX +paste+). Both frames must have the same
|
|
@@ -137,7 +207,10 @@ class CAFrame
|
|
|
137
207
|
|
|
138
208
|
# Detect columns that appear in 2+ frames (collisions across the K inputs).
|
|
139
209
|
name_count = Hash.new(0)
|
|
140
|
-
frames.each { |f| f.
|
|
210
|
+
frames.each { |f| f.column_names.each { |name| name_count[name] += 1 } }
|
|
211
|
+
# The first frame's index keeps its name as the result's row axis, so a
|
|
212
|
+
# column of that name collides with it as with a column.
|
|
213
|
+
name_count[first.axis_name] += 1 if first.index
|
|
141
214
|
collisions = name_count.select { |_, n| n > 1 }.keys
|
|
142
215
|
|
|
143
216
|
if collisions.any?
|
|
@@ -160,7 +233,7 @@ class CAFrame
|
|
|
160
233
|
|
|
161
234
|
cols = {}
|
|
162
235
|
frames.each_with_index do |f, i|
|
|
163
|
-
f.
|
|
236
|
+
f.column_names.each do |name|
|
|
164
237
|
key = collisions.include?(name) ? "#{name}#{suffixes[i]}" : name
|
|
165
238
|
if cols.key?(key)
|
|
166
239
|
raise ArgumentError,
|
|
@@ -173,8 +246,159 @@ class CAFrame
|
|
|
173
246
|
new(cols, axis_name: first.axis_name, index: first.index)
|
|
174
247
|
end
|
|
175
248
|
|
|
176
|
-
|
|
177
|
-
|
|
249
|
+
# A fixlen key holds a cell-width blob, so a left key and a right key of
|
|
250
|
+
# different widths share no value. Refuse it here, naming the column,
|
|
251
|
+
# rather than from inside the set operation the join runs.
|
|
252
|
+
private def join_check_key_widths(names, lkeys, rkeys, verb)
|
|
253
|
+
names.zip(lkeys, rkeys) do |name, l, r|
|
|
254
|
+
next unless l.data_type == CA_FIXLEN && r.data_type == CA_FIXLEN
|
|
255
|
+
next if l.face? || r.face? || l.bytes == r.bytes
|
|
256
|
+
raise CArray::DataTypeError,
|
|
257
|
+
"#{verb}: the key #{name.inspect} is fixlen #{l.bytes} bytes wide on the left " \
|
|
258
|
+
"and #{r.bytes} on the right; a fixlen value is a cell-width blob, so the widths must match"
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# The key column names of a join: one name, or an Array of names.
|
|
263
|
+
private def join_key_names(on, verb, option)
|
|
264
|
+
names = on.is_a?(Array) ? on : [on]
|
|
265
|
+
raise ArgumentError, "#{verb}: #{option} names no key column" if names.empty?
|
|
266
|
+
CAFrame.__send__(:refuse_repeated_names, names.map { |name| column_key(name, verb) },
|
|
267
|
+
verb, "#{option} names")
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Codes for the rows of both sides of a join on +lkeys+ / +rkeys+ (one
|
|
271
|
+
# array per key column): equal codes for equal keys, in 0...k, and none
|
|
272
|
+
# for a row with a masked key cell. Each key column gets a code in one
|
|
273
|
+
# set of distinct values of both sides, compared as the lookups compare
|
|
274
|
+
# them (type, unit and width reconciled). With several key columns a
|
|
275
|
+
# row's key is the tuple of its codes, coded again one column at a time
|
|
276
|
+
# so the number stays below the row count. Returns [parts, lcode, rcode,
|
|
277
|
+
# k], parts holding each column's [distinct values, left codes, right
|
|
278
|
+
# codes].
|
|
279
|
+
private def join_codes(lkeys, rkeys)
|
|
280
|
+
parts = lkeys.zip(rkeys).map do |lkey, rkey|
|
|
281
|
+
common = CArray.align_addr(lkey, rkey, join: :outer)[0]
|
|
282
|
+
[common, lkey.locate_addr(common), rkey.locate_addr(common)]
|
|
283
|
+
end
|
|
284
|
+
_, lcode, rcode = parts[0]
|
|
285
|
+
k = parts[0][0].elements
|
|
286
|
+
parts.drop(1).each do |common, lc, rc|
|
|
287
|
+
# keys: the two frames are coded on the values they share, which
|
|
288
|
+
# CACategorical.from_keys (one side's own values) cannot give
|
|
289
|
+
lt = lcode * common.elements + lc
|
|
290
|
+
rt = rcode * common.elements + rc
|
|
291
|
+
tuples = CArray.align_addr(lt, rt, join: :outer)[0]
|
|
292
|
+
lcode, rcode, k = lt.locate_addr(tuples), rt.locate_addr(tuples), tuples.elements
|
|
293
|
+
end
|
|
294
|
+
[parts, lcode, rcode, k]
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# The rows of a :semi or :anti join: whether each row's key code occurs
|
|
298
|
+
# among the other side's codes. A row without a code (a masked key cell)
|
|
299
|
+
# has no match.
|
|
300
|
+
private def join_filter(lkeys, rkeys, how)
|
|
301
|
+
_, lcode, rcode, k = join_codes(lkeys, rkeys)
|
|
302
|
+
matched = if k == 0
|
|
303
|
+
CArray.boolean(nrow).fill(false)
|
|
304
|
+
else
|
|
305
|
+
rcode.bincount(length: k).project(lcode).strip_mask(0).gt(0)
|
|
306
|
+
end
|
|
307
|
+
select_rows(how == :semi ? matched : matched.not)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# The other side's row for each left row of an as-of join with exact
|
|
311
|
+
# keys: the nearest by +lkey+ / +rkey+ among the right rows with the
|
|
312
|
+
# same code, as locate_nearest_addr finds it within one group.
|
|
313
|
+
#
|
|
314
|
+
# All groups are searched at once. The right rows are sorted by (code,
|
|
315
|
+
# key, row); sorting the left keys in among them by (code, key) counts,
|
|
316
|
+
# for each left row, the right rows of its group below it, and the
|
|
317
|
+
# nearest is one of the two right rows on either side of that count.
|
|
318
|
+
private def join_asof_by(lkey, rkey, lcode, rcode, k, direction, tolerance)
|
|
319
|
+
# direction: and tolerance: are checked here, on no rows, whether or
|
|
320
|
+
# not any group is in both frames.
|
|
321
|
+
lkey[0...0].locate_nearest_addr(rkey[0...0], direction: direction, tolerance: tolerance)
|
|
322
|
+
limit = tolerance && CArray::DistanceLimit.read(tolerance, lkey, "tolerance:")
|
|
323
|
+
addr = CArray.int64(lkey.elements).fill(UNDEF)
|
|
324
|
+
|
|
325
|
+
# The right rows that can be found: a code, and a key that is present
|
|
326
|
+
# and not NaN. Each group's rows in key order, a tie in row order.
|
|
327
|
+
rvalid = asof_searchable(rkey, rcode)
|
|
328
|
+
rrows = CArray.int64(rkey.elements).seq[rvalid].copy
|
|
329
|
+
return addr if rrows.elements == 0
|
|
330
|
+
rrows = rrows.project(CArray.sort_addr(rcode.project(rrows), rkey.project(rrows), rrows))
|
|
331
|
+
lvalid = asof_searchable(lkey, lcode)
|
|
332
|
+
lrows = CArray.int64(lkey.elements).seq[lvalid].copy
|
|
333
|
+
return addr if lrows.elements == 0
|
|
334
|
+
|
|
335
|
+
rk, qk = lkey.__send__(:nearest_keys, rkey.project(rrows), lkey.project(lrows))
|
|
336
|
+
rc = rcode.project(rrows).int64
|
|
337
|
+
qc = lcode.project(lrows).int64
|
|
338
|
+
n = rrows.elements
|
|
339
|
+
gcount = rc.bincount(length: k).int64
|
|
340
|
+
gstart = gcount.cumsum.int64 - gcount
|
|
341
|
+
|
|
342
|
+
# How many right rows of its group each left row has below it (at or
|
|
343
|
+
# below when +inclusive+): the left keys sorted in among the right
|
|
344
|
+
# ones, a tie putting the right one first when it counts.
|
|
345
|
+
below = lambda do |inclusive|
|
|
346
|
+
tag = CArray.int8(n + qk.elements)
|
|
347
|
+
tag[0...n] = inclusive ? 0 : 1
|
|
348
|
+
tag[n..-1] = inclusive ? 1 : 0
|
|
349
|
+
perm = CArray.sort_addr(CArray.concatenate([rc, qc]), CArray.concatenate([rk, qk]), tag)
|
|
350
|
+
seen = perm.lt(n).cumsum.int64
|
|
351
|
+
query = perm.ge(n)
|
|
352
|
+
count = CArray.int64(qk.elements)
|
|
353
|
+
count[perm[query] - n] = seen[query]
|
|
354
|
+
count - gstart.project(qc)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
size = gcount.project(qc)
|
|
358
|
+
local =
|
|
359
|
+
case direction
|
|
360
|
+
when :floor then below.(true) - 1
|
|
361
|
+
when :ceil then below.(false)
|
|
362
|
+
when :round
|
|
363
|
+
lt = below.(false)
|
|
364
|
+
start = gstart.project(qc)
|
|
365
|
+
has_lo = lt.gt(0)
|
|
366
|
+
has_hi = lt.lt(size)
|
|
367
|
+
lo = rk.project((start + lt - 1).tap { |a| a[has_lo.not] = UNDEF })
|
|
368
|
+
hi = rk.project((start + lt).tap { |a| a[has_hi.not] = UNDEF })
|
|
369
|
+
# an exact match takes its first reference; otherwise the nearer
|
|
370
|
+
# neighbour, the upper one on a tie
|
|
371
|
+
upper = (hi - qk).le(qk - lo).strip_mask(false)
|
|
372
|
+
upper[has_lo.not] = true
|
|
373
|
+
upper[has_hi.not] = false
|
|
374
|
+
upper[hi.eq(qk).strip_mask(false)] = true
|
|
375
|
+
lt - 1 + upper.int64
|
|
376
|
+
end
|
|
377
|
+
local[local.lt(0) | local.ge(size)] = UNDEF
|
|
378
|
+
found = rrows.project(gstart.project(qc) + local)
|
|
379
|
+
if limit
|
|
380
|
+
dist = (rkey.project(found) - lkey.project(lrows)).abs
|
|
381
|
+
found[dist > limit] = UNDEF
|
|
382
|
+
end
|
|
383
|
+
addr[lrows] = found
|
|
384
|
+
addr
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# The rows of a key that an as-of search can place: a group code, and a
|
|
388
|
+
# key cell that is neither masked nor NaN.
|
|
389
|
+
private def asof_searchable(key, code)
|
|
390
|
+
ok = code.is_not_masked.strip_mask(false)
|
|
391
|
+
ok &= key.is_not_masked.strip_mask(false) if key.has_mask?
|
|
392
|
+
ok &= key.is_nan.strip_mask(false).not if key.float?
|
|
393
|
+
ok
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# The left index keeps its name as the result's row axis, so a right
|
|
397
|
+
# column of that name collides with it as with a left column: it takes
|
|
398
|
+
# the right suffix, and there is no left column to take the left one.
|
|
399
|
+
private def join_name_plan(other, names, suffixes)
|
|
400
|
+
taken = @index ? column_names | [@axis_name] : column_names
|
|
401
|
+
collisions = (taken & other.column_names) - names
|
|
178
402
|
return { collisions: [], lsuf: nil, rsuf: nil } if collisions.empty?
|
|
179
403
|
|
|
180
404
|
if suffixes == false
|
|
@@ -193,6 +417,7 @@ class CAFrame
|
|
|
193
417
|
plan[:collisions].include?(name) ? "#{name}#{suffix}" : name
|
|
194
418
|
end
|
|
195
419
|
|
|
420
|
+
# names-check: the output is checked as it is built, a column at a time
|
|
196
421
|
private def put_join_column(cols, name, col)
|
|
197
422
|
if cols.key?(name)
|
|
198
423
|
raise ArgumentError,
|
|
@@ -202,41 +427,165 @@ class CAFrame
|
|
|
202
427
|
cols[name] = col
|
|
203
428
|
end
|
|
204
429
|
|
|
205
|
-
private def
|
|
206
|
-
join_by_addr(other, on, lkey.locate_addr(rkey), plan)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
private def join_by_addr(other, on, addr, plan)
|
|
430
|
+
private def join_by_addr(other, names, addr, plan)
|
|
210
431
|
cols = {}
|
|
211
|
-
|
|
212
|
-
other.
|
|
213
|
-
next if name
|
|
432
|
+
column_names.each { |name| put_join_column(cols, join_name(name, plan[:lsuf], plan), self[name]) }
|
|
433
|
+
other.column_names.each do |name|
|
|
434
|
+
next if names.include?(name)
|
|
214
435
|
put_join_column(cols, join_name(name, plan[:rsuf], plan), project_rows(other[name], addr))
|
|
215
436
|
end
|
|
216
437
|
CAFrame.new(cols, axis_name: @axis_name, index: @index)
|
|
217
438
|
end
|
|
218
439
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
440
|
+
# The rows of a key join: li / ri are the left / right row of each result
|
|
441
|
+
# row, UNDEF on the side a row has no match on.
|
|
442
|
+
private def join_pairs(other, names, lkeys, rkeys, how, plan)
|
|
443
|
+
parts, lcode, rcode, k = join_codes(lkeys, rkeys)
|
|
444
|
+
extra = nil
|
|
445
|
+
if how == :right
|
|
446
|
+
ri, li = join_expand(rcode, lcode, k, true)
|
|
447
|
+
else
|
|
448
|
+
li, ri = join_expand(lcode, rcode, k, how != :inner)
|
|
449
|
+
if how == :outer
|
|
450
|
+
lcount = lcode.bincount(length: k)
|
|
451
|
+
alone = rcode.is_masked | lcount.project(rcode).strip_mask(0).eq(0)
|
|
452
|
+
alone = alone.strip_mask(true)
|
|
453
|
+
extra = CArray.int64(rcode.elements).seq[alone]
|
|
454
|
+
if extra.elements > 0
|
|
455
|
+
undef_rows = CArray.int64(extra.elements).fill(UNDEF)
|
|
456
|
+
li = CArray.concatenate([li, undef_rows])
|
|
457
|
+
ri = CArray.concatenate([ri, extra])
|
|
458
|
+
else
|
|
459
|
+
extra = nil
|
|
460
|
+
end
|
|
227
461
|
end
|
|
228
462
|
end
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
463
|
+
every_left = how == :left || how == :inner
|
|
464
|
+
identity = every_left && li.elements == nrow &&
|
|
465
|
+
(nrow == 0 || li.eq(CArray.int64(nrow).seq).all)
|
|
466
|
+
rows = { li: li, ri: ri, extra: extra, how: how, every_left: every_left, identity: identity }
|
|
467
|
+
cols = {}
|
|
468
|
+
column_names.each do |name|
|
|
469
|
+
j = names.index(name)
|
|
470
|
+
col = if j then joined_key(j, parts, lkeys, rkeys, rows)
|
|
471
|
+
elsif identity then self[name]
|
|
472
|
+
elsif every_left then self[name][li, *([nil] * (self[name].ndim - 1))]
|
|
473
|
+
else project_rows(self[name], li)
|
|
474
|
+
end
|
|
475
|
+
name = join_name(name, plan[:lsuf], plan) unless j
|
|
476
|
+
put_join_column(cols, name, col)
|
|
477
|
+
end
|
|
478
|
+
other.column_names.each do |name|
|
|
479
|
+
next if names.include?(name)
|
|
480
|
+
put_join_column(cols, join_name(name, plan[:rsuf], plan), project_rows(other[name], ri))
|
|
232
481
|
end
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
|
|
236
|
-
new_index =
|
|
482
|
+
# A key that is the left index is the result's index, as a key column
|
|
483
|
+
# is a column of it.
|
|
484
|
+
ij = @index && !@columns.key?(@axis_name) ? names.index(@axis_name) : nil
|
|
485
|
+
new_index = if @index.nil? then nil
|
|
486
|
+
elsif ij then joined_key(ij, parts, lkeys, rkeys, rows)
|
|
487
|
+
elsif identity then @index
|
|
488
|
+
elsif every_left then @index[li]
|
|
489
|
+
else project_rows(@index, li)
|
|
490
|
+
end
|
|
237
491
|
CAFrame.new(cols, axis_name: @axis_name, index: new_index)
|
|
238
492
|
end
|
|
239
493
|
|
|
494
|
+
# The key +j+ of each row of a key join: the left key on the rows from the
|
|
495
|
+
# left, and on an unmatched right row of a right or outer join the right
|
|
496
|
+
# key, so a key is kept once and is never UNDEF where a row has one.
|
|
497
|
+
private def joined_key(j, parts, lkeys, rkeys, rows)
|
|
498
|
+
li, ri = rows[:li], rows[:ri]
|
|
499
|
+
return lkeys[j] if rows[:identity]
|
|
500
|
+
return lkeys[j][li] if rows[:every_left]
|
|
501
|
+
return project_rows(rkeys[j], ri) if rows[:how] == :right
|
|
502
|
+
common, lc, rc = parts[j]
|
|
503
|
+
extra = rows[:extra]
|
|
504
|
+
code = if extra
|
|
505
|
+
nleft = li.elements - extra.elements
|
|
506
|
+
CArray.concatenate([lc.project(li[0...nleft]), rc.project(extra)])
|
|
507
|
+
else
|
|
508
|
+
lc.project(li)
|
|
509
|
+
end
|
|
510
|
+
# The key takes the attributes both sides show alike, as a
|
|
511
|
+
# concatenation of the two would.
|
|
512
|
+
shared = CArray.__send__(:attrs_shared_by, [lkeys[j], rkeys[j]])
|
|
513
|
+
key = joined_key_kind(common.project(code), lkeys[j], rkeys[j])
|
|
514
|
+
key.tap { |c| c.set_attrs(shared) unless shared.empty? }
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# The kind of a key column holding values of both sides, the same
|
|
518
|
+
# whichever side is the receiver: a Face both sides share stays (a
|
|
519
|
+
# categorical with the labels of both, the left's first), and keys of
|
|
520
|
+
# two different Faces, or of a Face and a plain array, are objects.
|
|
521
|
+
private def joined_key_kind(col, lkey, rkey)
|
|
522
|
+
lface = lkey.class if lkey.face?
|
|
523
|
+
rface = rkey.class if rkey.face?
|
|
524
|
+
return col if lface.nil? && rface.nil?
|
|
525
|
+
if lface == rface
|
|
526
|
+
return col unless lface <= CACategorical
|
|
527
|
+
values = CArray.object(col.elements)
|
|
528
|
+
values[] = col.to_a
|
|
529
|
+
return values.categorize(labels: lkey.labels | rkey.labels)
|
|
530
|
+
end
|
|
531
|
+
return col if !col.face? && col.object?
|
|
532
|
+
CArray.object(col.elements).tap { |o| o[] = col.to_a }
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# Pairs a side's rows (codes +acode+) with the other side's (+bcode+), in
|
|
536
|
+
# a-row order and, for one a-row, in b-row order. An a-row with no match
|
|
537
|
+
# comes once with no b-row when +keep_alone+. Returns [ai, bi].
|
|
538
|
+
private def join_expand(acode, bcode, k, keep_alone)
|
|
539
|
+
na, nb = acode.elements, bcode.elements
|
|
540
|
+
bcount = bcode.bincount(length: k).int64
|
|
541
|
+
if k == 0 || bcount.max <= 1
|
|
542
|
+
return join_expand_unique(acode, bcode, k, bcount, keep_alone)
|
|
543
|
+
end
|
|
544
|
+
# b's rows by code, each code's rows in b-row order.
|
|
545
|
+
border = CArray.sort_addr(bcode, CArray.int64(nb).seq)
|
|
546
|
+
bstart = bcount.cumsum.int64 - bcount
|
|
547
|
+
matches = bcount.project(acode).strip_mask(0)
|
|
548
|
+
reps = keep_alone ? matches.pmax(1) : matches
|
|
549
|
+
total = reps.sum.to_i
|
|
550
|
+
return [CArray.int64(0), CArray.int64(0)] if total == 0
|
|
551
|
+
# Each a-row repeated reps times: mark where each run starts and count.
|
|
552
|
+
rows = CArray.int64(na).seq[reps.gt(0)]
|
|
553
|
+
first = (reps.cumsum.int64 - reps)[reps.gt(0)]
|
|
554
|
+
mark = CArray.int64(total).fill(0)
|
|
555
|
+
mark[first] = 1
|
|
556
|
+
run = mark.cumsum.int64 - 1
|
|
557
|
+
ai = rows[run]
|
|
558
|
+
j = CArray.int64(total).seq - first[run]
|
|
559
|
+
bi = CArray.int64(total).fill(UNDEF)
|
|
560
|
+
hit = matches.project(ai).gt(0)
|
|
561
|
+
if hit.any
|
|
562
|
+
bi[hit] = border.project(bstart.project(acode.project(ai[hit])) + j[hit])
|
|
563
|
+
end
|
|
564
|
+
[ai, bi]
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
# join_expand when each code is on at most one b-row: an a-row pairs
|
|
568
|
+
# with that row or with none, so no a-row repeats. The b-row of each
|
|
569
|
+
# code is looked up once, without the repeat-and-scatter of the
|
|
570
|
+
# general case; the pairs come in the same order.
|
|
571
|
+
private def join_expand_unique(acode, bcode, k, bcount, keep_alone)
|
|
572
|
+
na, nb = acode.elements, bcode.elements
|
|
573
|
+
brow = CArray.int64(k).fill(UNDEF)
|
|
574
|
+
if nb > 0
|
|
575
|
+
present = bcode.is_not_masked
|
|
576
|
+
brow[bcode[present]] = CArray.int64(nb).seq![present]
|
|
577
|
+
end
|
|
578
|
+
if keep_alone
|
|
579
|
+
return [CArray.int64(0), CArray.int64(0)] if na == 0
|
|
580
|
+
[CArray.int64(na).seq!, brow.project(acode)]
|
|
581
|
+
else
|
|
582
|
+
hit = bcount.project(acode).strip_mask(0).gt(0)
|
|
583
|
+
ai = CArray.int64(na).seq![hit].copy
|
|
584
|
+
return [CArray.int64(0), CArray.int64(0)] if ai.elements == 0
|
|
585
|
+
[ai, brow.project(acode.project(ai))]
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
240
589
|
private def project_rows(col, addr)
|
|
241
590
|
return col.project(addr) if col.ndim == 1
|
|
242
591
|
trailing = col.shape[1..]
|