carray 3.0.1 → 3.0.3
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 +824 -0
- data/README.md +5 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +258 -4
- data/ext/ca_axis_group.c +368 -184
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +90 -41
- data/ext/ca_compare.c +8 -2
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +27 -11
- data/ext/ca_kernel_iterator.c +960 -245
- data/ext/ca_kernel_iterator.h +352 -181
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +194 -42
- data/ext/ca_obj_bincmp.c +152 -121
- data/ext/ca_obj_binop.c +107 -210
- data/ext/ca_obj_bitarray.c +37 -24
- data/ext/ca_obj_bitfield.c +95 -62
- data/ext/ca_obj_block.c +5 -4
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +180 -50
- data/ext/ca_obj_face.c +428 -62
- data/ext/ca_obj_face.h +107 -47
- data/ext/ca_obj_fake.c +21 -50
- data/ext/ca_obj_farray.c +1 -0
- data/ext/ca_obj_field.c +16 -3
- data/ext/ca_obj_fixlen_string.c +39 -26
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +235 -55
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +551 -233
- data/ext/ca_obj_record.c +51 -16
- data/ext/ca_obj_reduce.c +5 -8
- data/ext/ca_obj_refer.c +117 -33
- data/ext/ca_obj_remap.c +57 -17
- data/ext/ca_obj_repeat.c +21 -5
- data/ext/ca_obj_roll.c +115 -11
- data/ext/ca_obj_select.c +88 -75
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -123
- data/ext/ca_obj_stride.c +267 -35
- data/ext/ca_obj_string.c +25 -22
- data/ext/ca_obj_tile.c +32 -16
- data/ext/ca_obj_time.c +23 -25
- data/ext/ca_obj_timedelta.c +21 -23
- data/ext/ca_obj_transpose.c +12 -3
- data/ext/ca_obj_triop.c +110 -117
- data/ext/ca_obj_window.c +157 -29
- data/ext/ca_op_ipower.c +25 -13
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +385 -198
- data/ext/ca_sweep_engine.h +31 -23
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +463 -0
- data/ext/carray.h +90 -28
- data/ext/carray_access.c +487 -231
- data/ext/carray_address_basis.c +731 -0
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +49 -17
- data/ext/carray_call_cfunc.c +667 -511
- data/ext/carray_cast.c +1556 -247
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +84 -38
- data/ext/carray_core.c +591 -119
- data/ext/carray_count.c +148 -96
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +604 -291
- data/ext/carray_generate.c +76 -40
- data/ext/carray_histogram.c +152 -94
- data/ext/carray_hold.c +2 -1
- data/ext/carray_index_classifier.c +63 -96
- data/ext/carray_internal.h +400 -1
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +88 -66
- data/ext/carray_mask.c +705 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +263 -63
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1093 -363
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +582 -204
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +183 -70
- data/ext/carray_sort.c +235 -240
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +99 -21
- data/ext/carray_test.c +167 -64
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_utils.c +363 -98
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +103 -118
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1349 -851
- data/ext/ruby_carray.c +17 -2
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +39 -0
- data/lib/carray/autoload_carray.rb +24 -3
- data/lib/carray/autoload_method_extension.rb +18 -0
- data/lib/carray/axis_group.rb +245 -20
- data/lib/carray/basics.rb +95 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +200 -54
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +244 -59
- data/lib/carray/categorical_iterator.rb +391 -757
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +177 -27
- data/lib/carray/construct.rb +40 -0
- data/lib/carray/data_type_extension.rb +3 -0
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +590 -76
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +205 -35
- data/lib/carray/frame/group.rb +199 -40
- data/lib/carray/frame/io.rb +452 -60
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +54 -19
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +726 -56
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +111 -2
- data/lib/carray/fusion.rb +229 -38
- data/lib/carray/inspect.rb +71 -20
- data/lib/carray/iterator.rb +157 -2
- data/lib/carray/lazy.rb +54 -67
- data/lib/carray/mask_gap_fill.rb +66 -50
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +40 -7
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bincount.rb +2 -2
- data/lib/carray/methods/bit_string.rb +14 -3
- data/lib/carray/methods/composition.rb +16 -10
- data/lib/carray/methods/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- data/lib/carray/methods/gather_nd.rb +13 -2
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_in.rb +130 -6
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +235 -21
- data/lib/carray/methods/mask_duplicates.rb +35 -1
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +22 -1
- data/lib/carray/methods/repeat.rb +91 -0
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +52 -3
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/runtime.rb +14 -11
- data/lib/carray/segment_iterator.rb +990 -0
- data/lib/carray/serialize.rb +197 -27
- data/lib/carray/slab_iterator.rb +109 -40
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +9 -4
- data/lib/carray/struct.rb +142 -51
- data/lib/carray/struct_builder.rb +110 -14
- data/lib/carray/table.rb +2 -2
- data/lib/carray/time.rb +535 -105
- data/lib/carray/window_iterator.rb +259 -82
- data/lib/carray.rb +4 -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_block.rb +2 -7
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +1 -1
- data/yard-stubs/ca_obj_refer.rb +3 -3
- data/yard-stubs/ca_obj_roll.rb +0 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +11 -4
- data/yard-stubs/carray_access.rb +6 -5
- data/yard-stubs/carray_broadcast.rb +2 -2
- data/yard-stubs/carray_cast.rb +78 -43
- data/yard-stubs/carray_conversion.rb +13 -1
- data/yard-stubs/carray_copy.rb +8 -3
- data/yard-stubs/carray_core.rb +3 -83
- data/yard-stubs/carray_count.rb +16 -2
- data/yard-stubs/carray_lazy.rb +210 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +1499 -4
- data/yard-stubs/carray_median_percentile.rb +26 -8
- data/yard-stubs/carray_order.rb +43 -82
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +11 -8
- data/yard-stubs/carray_sort.rb +50 -8
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -19
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_build_flags.h +0 -3
- data/ext/carray_config.h +0 -28
- data/ext/carray_kernels_bincmp.c +0 -4446
- data/ext/carray_kernels_binop.c +0 -11001
- data/ext/carray_kernels_init.c +0 -1131
- data/ext/carray_kernels_map.c +0 -3467
- data/ext/carray_kernels_moncmp.c +0 -2097
- data/ext/carray_kernels_monop.c +0 -18313
- data/ext/carray_kernels_reduce_aggregate.c +0 -25837
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14593
- data/ext/carray_kernels_reduce_extreme.c +0 -16948
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32138
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
data/lib/carray/frame/join.rb
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
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
|
+
|
|
9
11
|
class CAFrame
|
|
10
12
|
# Default disambiguation for non-key columns present on both sides (§12-C):
|
|
11
13
|
# left gets "_left", right gets "_right". +_left+/+_right+ read clearer than
|
|
@@ -13,13 +15,21 @@ class CAFrame
|
|
|
13
15
|
DEFAULT_JOIN_SUFFIXES = ["_left", "_right"].freeze
|
|
14
16
|
private_constant :DEFAULT_JOIN_SUFFIXES
|
|
15
17
|
|
|
16
|
-
# Join +other+ on a shared key column
|
|
18
|
+
# Join +other+ on a shared key column, as SQL joins: each pair of a left
|
|
19
|
+
# row and a right row with the same key gives one row, so a key on k left
|
|
20
|
+
# rows and m right rows gives k * m rows. A masked key matches nothing.
|
|
21
|
+
#
|
|
22
|
+
# how: :left (default) — every pair, and each left row with no match
|
|
23
|
+
# once, its right columns UNDEF.
|
|
24
|
+
# how: :inner — every pair.
|
|
25
|
+
# how: :right — every pair, and each right row with no match once.
|
|
26
|
+
# how: :outer — every pair, each unmatched left row, then each unmatched
|
|
27
|
+
# right row.
|
|
17
28
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# the +on+ column.
|
|
29
|
+
# Rows come in left-row order (right-row order for :right), the pairs of
|
|
30
|
+
# one row in the other side's order. The left columns are views of this
|
|
31
|
+
# frame's, picked by row, wherever every row comes from the left (:left,
|
|
32
|
+
# :inner); the right columns are gathered into new arrays.
|
|
23
33
|
#
|
|
24
34
|
# A non-key column present on both sides collides. The key column (+on+) is
|
|
25
35
|
# kept once, never suffixed. By default the collision is resolved by suffixing
|
|
@@ -28,18 +38,14 @@ class CAFrame
|
|
|
28
38
|
# raise instead. Rename afterward with +rename+ if needed (§12-C).
|
|
29
39
|
def join(other, on:, how: :left, suffixes: DEFAULT_JOIN_SUFFIXES)
|
|
30
40
|
on = on.to_s
|
|
31
|
-
lkey = self[on]
|
|
32
|
-
rkey = other[on]
|
|
41
|
+
lkey = join_key(self[on], on, "join")
|
|
42
|
+
rkey = join_key(other[on], on, "join")
|
|
33
43
|
plan = join_name_plan(other, on, suffixes)
|
|
34
44
|
|
|
35
|
-
|
|
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
|
|
45
|
+
unless %i[left inner outer right].include?(how)
|
|
41
46
|
raise ArgumentError, "unknown join mode #{how.inspect}"
|
|
42
47
|
end
|
|
48
|
+
join_pairs(other, on, lkey, rkey, how, plan)
|
|
43
49
|
end
|
|
44
50
|
|
|
45
51
|
# As-of (nearest-key) left join for irregular series (memo §11.7). Each
|
|
@@ -47,16 +53,20 @@ class CAFrame
|
|
|
47
53
|
# locate_nearest_addr (the per-row, row-preserving counterpart of
|
|
48
54
|
# align_nearest_addr); +direction:+ follows CArray (:floor = most recent
|
|
49
55
|
# at-or-before, :ceil = next, :round = nearest), and rows with no match
|
|
50
|
-
# in
|
|
51
|
-
#
|
|
56
|
+
# in that direction or beyond +tolerance:+ come back UNDEF. +tolerance:+
|
|
57
|
+
# is a number for a numeric key and a duration for a time key
|
|
58
|
+
# ("10 minutes", or a CATimedelta value). Same wiring as the left join,
|
|
59
|
+
# only the address primitive differs.
|
|
52
60
|
def join_asof(other, on:, direction: :floor, tolerance: nil, suffixes: DEFAULT_JOIN_SUFFIXES)
|
|
53
61
|
on = on.to_s
|
|
54
62
|
plan = join_name_plan(other, on, suffixes)
|
|
55
|
-
|
|
63
|
+
lkey = join_key(self[on], on, "join_asof")
|
|
64
|
+
rkey = join_key(other[on], on, "join_asof")
|
|
65
|
+
addr = lkey.locate_nearest_addr(rkey, direction: direction, tolerance: tolerance)
|
|
56
66
|
join_by_addr(other, on, addr, plan)
|
|
57
67
|
end
|
|
58
68
|
|
|
59
|
-
# Conform every
|
|
69
|
+
# Conform every column to an externally supplied reference key set (the
|
|
60
70
|
# asymmetric sibling of +join+; pandas +reindex+). Given a +reference+ array
|
|
61
71
|
# of key values, each column is gathered onto it by exact key match
|
|
62
72
|
# (+locate_addr+): the aligned key column (or index) becomes the reference
|
|
@@ -71,16 +81,25 @@ class CAFrame
|
|
|
71
81
|
# df.align("time", reftime) # reftime = a CArray of reference key values
|
|
72
82
|
#
|
|
73
83
|
# +key+ may be a column name or the index axis name. +reference+ is a CArray
|
|
74
|
-
#
|
|
75
|
-
#
|
|
84
|
+
# or an Array; its keys are matched against the source key by value. An
|
|
85
|
+
# Array is read as values of the key's kind, as +is_in+ reads one: numbers
|
|
86
|
+
# against a numeric key, Time / Date / String instants against a time key.
|
|
76
87
|
def align(key, reference)
|
|
77
88
|
key = key.to_s
|
|
78
|
-
ref = reference.is_a?(CArray) ? reference : reference.to_ca
|
|
79
89
|
on_index = !@columns.key?(key) && @axis_name == key && @index
|
|
80
|
-
|
|
90
|
+
keys = on_index ? @index : join_key(self[key], key, "align")
|
|
91
|
+
# An Array reference is read as values of the key's kind, as is_in reads
|
|
92
|
+
# one: 1 and 2.5 against a float key are numbers, and Time or String
|
|
93
|
+
# values against a time key are instants.
|
|
94
|
+
ref = reference.is_a?(CArray) ? reference : keys.send(:promote_value_set, reference)[1]
|
|
95
|
+
unless ref.ndim == 1
|
|
96
|
+
raise ArgumentError,
|
|
97
|
+
"align: the reference has shape #{ref.shape.inspect}; give one key value per row of the result"
|
|
98
|
+
end
|
|
99
|
+
addr = ref.locate_addr(keys)
|
|
81
100
|
|
|
82
101
|
cols = {}
|
|
83
|
-
|
|
102
|
+
column_names.each { |name| cols[name] = project_rows(self[name], addr) }
|
|
84
103
|
if on_index
|
|
85
104
|
CAFrame.new(cols, axis_name: key, index: ref)
|
|
86
105
|
else
|
|
@@ -90,7 +109,7 @@ class CAFrame
|
|
|
90
109
|
end
|
|
91
110
|
end
|
|
92
111
|
|
|
93
|
-
# Paste +other+'s
|
|
112
|
+
# Paste +other+'s columns beside this frame's, matched by **row position**
|
|
94
113
|
# (memo §12-C) — a keyless column merge (the column-direction counterpart of
|
|
95
114
|
# the row-stacking +concat+, and the positional counterpart of the key-aligned
|
|
96
115
|
# +join+; named after the UNIX +paste+). Both frames must have the same
|
|
@@ -137,7 +156,7 @@ class CAFrame
|
|
|
137
156
|
|
|
138
157
|
# Detect columns that appear in 2+ frames (collisions across the K inputs).
|
|
139
158
|
name_count = Hash.new(0)
|
|
140
|
-
frames.each { |f| f.
|
|
159
|
+
frames.each { |f| f.column_names.each { |name| name_count[name] += 1 } }
|
|
141
160
|
collisions = name_count.select { |_, n| n > 1 }.keys
|
|
142
161
|
|
|
143
162
|
if collisions.any?
|
|
@@ -160,7 +179,7 @@ class CAFrame
|
|
|
160
179
|
|
|
161
180
|
cols = {}
|
|
162
181
|
frames.each_with_index do |f, i|
|
|
163
|
-
f.
|
|
182
|
+
f.column_names.each do |name|
|
|
164
183
|
key = collisions.include?(name) ? "#{name}#{suffixes[i]}" : name
|
|
165
184
|
if cols.key?(key)
|
|
166
185
|
raise ArgumentError,
|
|
@@ -173,8 +192,17 @@ class CAFrame
|
|
|
173
192
|
new(cols, axis_name: first.axis_name, index: first.index)
|
|
174
193
|
end
|
|
175
194
|
|
|
195
|
+
# The key column of a join, checked to give each row one value. An N-D key
|
|
196
|
+
# would be matched cell by cell rather than row by row.
|
|
197
|
+
private def join_key(col, on, verb)
|
|
198
|
+
return col if col.ndim == 1
|
|
199
|
+
raise ArgumentError,
|
|
200
|
+
"#{verb}: the key #{on.inspect} has shape #{col.shape.inspect}, not one value per row; " \
|
|
201
|
+
"#{verb} on one of its components (e.g. df[name][nil, 0])"
|
|
202
|
+
end
|
|
203
|
+
|
|
176
204
|
private def join_name_plan(other, on, suffixes)
|
|
177
|
-
collisions = (
|
|
205
|
+
collisions = (column_names & other.column_names) - [on]
|
|
178
206
|
return { collisions: [], lsuf: nil, rsuf: nil } if collisions.empty?
|
|
179
207
|
|
|
180
208
|
if suffixes == false
|
|
@@ -202,41 +230,101 @@ class CAFrame
|
|
|
202
230
|
cols[name] = col
|
|
203
231
|
end
|
|
204
232
|
|
|
205
|
-
private def join_left(other, on, lkey, rkey, plan)
|
|
206
|
-
join_by_addr(other, on, lkey.locate_addr(rkey), plan)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
233
|
private def join_by_addr(other, on, addr, plan)
|
|
210
234
|
cols = {}
|
|
211
|
-
|
|
212
|
-
other.
|
|
235
|
+
column_names.each { |name| put_join_column(cols, join_name(name, plan[:lsuf], plan), self[name]) }
|
|
236
|
+
other.column_names.each do |name|
|
|
213
237
|
next if name == on
|
|
214
238
|
put_join_column(cols, join_name(name, plan[:rsuf], plan), project_rows(other[name], addr))
|
|
215
239
|
end
|
|
216
240
|
CAFrame.new(cols, axis_name: @axis_name, index: @index)
|
|
217
241
|
end
|
|
218
242
|
|
|
219
|
-
|
|
220
|
-
|
|
243
|
+
# The rows of a key join: li / ri are the left / right row of each result
|
|
244
|
+
# row, UNDEF on the side a row has no match on.
|
|
245
|
+
private def join_pairs(other, on, lkey, rkey, how, plan)
|
|
246
|
+
# Both keys get a code in one set of distinct values, compared as the
|
|
247
|
+
# lookups compare them (type, unit and width reconciled); a masked key
|
|
248
|
+
# gets none and matches nothing.
|
|
249
|
+
common = CArray.align_addr(lkey, rkey, join: :outer)[0]
|
|
250
|
+
lcode = lkey.locate_addr(common)
|
|
251
|
+
rcode = rkey.locate_addr(common)
|
|
252
|
+
if how == :right
|
|
253
|
+
ri, li = join_expand(rcode, lcode, common.elements, true)
|
|
254
|
+
else
|
|
255
|
+
li, ri = join_expand(lcode, rcode, common.elements, how != :inner)
|
|
256
|
+
if how == :outer
|
|
257
|
+
lcount = lcode.bincount(length: common.elements)
|
|
258
|
+
alone = rcode.is_masked | lcount.project(rcode).strip_mask(0).eq(0)
|
|
259
|
+
alone = alone.strip_mask(true)
|
|
260
|
+
extra = CArray.int64(rcode.elements).seq[alone]
|
|
261
|
+
if extra.elements > 0
|
|
262
|
+
undef_rows = CArray.int64(extra.elements).fill(UNDEF)
|
|
263
|
+
li = CArray.concatenate([li, undef_rows])
|
|
264
|
+
ri = CArray.concatenate([ri, extra])
|
|
265
|
+
code = CArray.concatenate([lcode.project(li[0...(li.elements - extra.elements)]),
|
|
266
|
+
rcode.project(extra)])
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
every_left = how == :left || how == :inner
|
|
271
|
+
identity = every_left && li.elements == nrow &&
|
|
272
|
+
(nrow == 0 || li.eq(CArray.int64(nrow).seq).all)
|
|
221
273
|
cols = {}
|
|
222
|
-
|
|
223
|
-
if name
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
274
|
+
column_names.each do |name|
|
|
275
|
+
col = if identity then self[name]
|
|
276
|
+
elsif every_left then self[name][li, *([nil] * (self[name].ndim - 1))]
|
|
277
|
+
elsif name == on then nil
|
|
278
|
+
else project_rows(self[name], li)
|
|
279
|
+
end
|
|
280
|
+
if name == on && col.nil?
|
|
281
|
+
col = how == :right ? project_rows(rkey, ri) :
|
|
282
|
+
common.project(code || lcode.project(li))
|
|
227
283
|
end
|
|
284
|
+
name = join_name(name, plan[:lsuf], plan) unless name == on
|
|
285
|
+
put_join_column(cols, name, col)
|
|
228
286
|
end
|
|
229
|
-
other.
|
|
287
|
+
other.column_names.each do |name|
|
|
230
288
|
next if name == on
|
|
231
|
-
put_join_column(cols, join_name(name, plan[:rsuf], plan), project_rows(other[name],
|
|
289
|
+
put_join_column(cols, join_name(name, plan[:rsuf], plan), project_rows(other[name], ri))
|
|
232
290
|
end
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
291
|
+
new_index = if @index.nil? then nil
|
|
292
|
+
elsif identity then @index
|
|
293
|
+
elsif every_left then @index[li]
|
|
294
|
+
else project_rows(@index, li)
|
|
295
|
+
end
|
|
237
296
|
CAFrame.new(cols, axis_name: @axis_name, index: new_index)
|
|
238
297
|
end
|
|
239
298
|
|
|
299
|
+
# Pairs a side's rows (codes +acode+) with the other side's (+bcode+), in
|
|
300
|
+
# a-row order and, for one a-row, in b-row order. An a-row with no match
|
|
301
|
+
# comes once with no b-row when +keep_alone+. Returns [ai, bi].
|
|
302
|
+
private def join_expand(acode, bcode, k, keep_alone)
|
|
303
|
+
na, nb = acode.elements, bcode.elements
|
|
304
|
+
# b's rows by code, each code's rows in b-row order.
|
|
305
|
+
border = CArray.sort_addr(bcode, CArray.int64(nb).seq)
|
|
306
|
+
bcount = bcode.bincount(length: k).int64
|
|
307
|
+
bstart = bcount.cumsum.int64 - bcount
|
|
308
|
+
matches = bcount.project(acode).strip_mask(0)
|
|
309
|
+
reps = keep_alone ? matches.pmax(1) : matches
|
|
310
|
+
total = reps.sum.to_i
|
|
311
|
+
return [CArray.int64(0), CArray.int64(0)] if total == 0
|
|
312
|
+
# Each a-row repeated reps times: mark where each run starts and count.
|
|
313
|
+
rows = CArray.int64(na).seq[reps.gt(0)]
|
|
314
|
+
first = (reps.cumsum.int64 - reps)[reps.gt(0)]
|
|
315
|
+
mark = CArray.int64(total).fill(0)
|
|
316
|
+
mark[first] = 1
|
|
317
|
+
run = mark.cumsum.int64 - 1
|
|
318
|
+
ai = rows[run]
|
|
319
|
+
j = CArray.int64(total).seq - first[run]
|
|
320
|
+
bi = CArray.int64(total).fill(UNDEF)
|
|
321
|
+
hit = matches.project(ai).gt(0)
|
|
322
|
+
if hit.any
|
|
323
|
+
bi[hit] = border.project(bstart.project(acode.project(ai[hit])) + j[hit])
|
|
324
|
+
end
|
|
325
|
+
[ai, bi]
|
|
326
|
+
end
|
|
327
|
+
|
|
240
328
|
private def project_rows(col, addr)
|
|
241
329
|
return col.project(addr) if col.ndim == 1
|
|
242
330
|
trailing = col.shape[1..]
|
data/lib/carray/frame/records.rb
CHANGED
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
|
|
17
17
|
class CAFrame
|
|
18
18
|
# Build a frame from an Array of row Hashes. Column set is the union of keys
|
|
19
|
-
# in first-appearance order; keys are stringified. +types:+
|
|
20
|
-
#
|
|
21
|
-
|
|
19
|
+
# in first-appearance order; keys are stringified. +types:+ takes
|
|
20
|
+
# anything +cast+ takes as a map (a name => type map, :infer, or a map with
|
|
21
|
+
# +default:+) and casts the built frame with it, with +on_error:+ as for
|
|
22
|
+
# +cast+.
|
|
23
|
+
def self.from_records(records, types: nil, on_error: :mask)
|
|
22
24
|
unless records.is_a?(Array) && records.all? { |r| r.is_a?(Hash) }
|
|
23
25
|
raise ArgumentError, "from_records expects an Array of Hashes"
|
|
24
26
|
end
|
|
@@ -33,7 +35,7 @@ class CAFrame
|
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
frame = new(cols)
|
|
36
|
-
frame.cast(types)
|
|
38
|
+
frame.cast(types, on_error: on_error, option_name: :types) unless types.nil?
|
|
37
39
|
frame
|
|
38
40
|
end
|
|
39
41
|
|
|
@@ -59,40 +61,73 @@ class CAFrame
|
|
|
59
61
|
# native scalar column (nil -> UNDEF); anything else -> object column.
|
|
60
62
|
def self.build_record_column(values, n)
|
|
61
63
|
present = values.reject(&:nil?)
|
|
62
|
-
return CArray.object(n) { values } if present.empty?
|
|
64
|
+
return mask_missing(CArray.object(n) { values }) if present.empty?
|
|
63
65
|
|
|
64
66
|
if present.all? { |v| v.is_a?(Array) || v.is_a?(CArray) }
|
|
65
67
|
build_nd_column(values, present, n)
|
|
66
68
|
else
|
|
67
69
|
type = numeric_leaf_type(present)
|
|
68
70
|
col = CArray.object(n) { values }
|
|
69
|
-
type ? col.to_type(type) : col
|
|
71
|
+
type ? col.to_type(type) : mask_missing(col)
|
|
70
72
|
end
|
|
71
73
|
end
|
|
72
74
|
private_class_method :build_record_column
|
|
73
75
|
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
76
|
+
# A missing cell is UNDEF, not a Ruby nil sitting in a cell. to_records
|
|
77
|
+
# writes a masked cell as nil, so nil on the way back in is the only
|
|
78
|
+
# spelling a missing cell has; to_type does this conversion for a numeric
|
|
79
|
+
# column, and an object column would otherwise keep the nil as a value --
|
|
80
|
+
# a row with no label coming back as a row labelled nil. The CSV reader
|
|
81
|
+
# takes the same position (see build_frame in io.rb).
|
|
82
|
+
def self.mask_missing(col)
|
|
83
|
+
col[:eq, nil] = UNDEF
|
|
84
|
+
col
|
|
85
|
+
end
|
|
86
|
+
private_class_method :mask_missing
|
|
87
|
+
|
|
88
|
+
# Stack array cells of one shape into an (N, *shape) column via an object
|
|
89
|
+
# fill + to_type (nil rows -> UNDEF, int/float by leaf); a cell may itself
|
|
90
|
+
# be nested, an (N, L, M) column from [[..], [..]] cells. Cells of
|
|
91
|
+
# different shapes, nesting that is not rectangular, or non-numeric leaves
|
|
92
|
+
# fall back to a 1-D object column of the raw cells.
|
|
77
93
|
def self.build_nd_column(values, present, n)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
shapes = present.map { |v| v.is_a?(CArray) ? v.shape : nested_shape(v) }
|
|
95
|
+
shape = shapes.first
|
|
96
|
+
unless shape && shapes.all? { |x| x == shape }
|
|
97
|
+
return mask_missing(CArray.object(n) { values })
|
|
98
|
+
end
|
|
81
99
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
type
|
|
100
|
+
blank = shape.reverse.inject(nil) { |cell, d| Array.new(d) { cell } }
|
|
101
|
+
nested = values.map { |v| v.nil? ? blank : (v.is_a?(CArray) ? v.to_a : v) }
|
|
102
|
+
leaves = nested.flatten.compact
|
|
103
|
+
type = leaves.empty? ? nil : numeric_leaf_type(leaves)
|
|
104
|
+
table = CArray.object(n, *shape) { nested }
|
|
105
|
+
type ? table.to_type(type) : mask_missing(table)
|
|
86
106
|
end
|
|
87
107
|
private_class_method :build_nd_column
|
|
88
108
|
|
|
89
|
-
#
|
|
90
|
-
#
|
|
109
|
+
# The shape of a rectangular nested Array, or nil when it is not
|
|
110
|
+
# rectangular. A leaf (anything but an Array) has shape [].
|
|
111
|
+
def self.nested_shape(v)
|
|
112
|
+
return [] unless v.is_a?(Array)
|
|
113
|
+
inner = v.map { |e| nested_shape(e) }
|
|
114
|
+
return [v.size] if v.empty?
|
|
115
|
+
return nil unless inner.first && inner.all? { |x| x == inner.first }
|
|
116
|
+
[v.size, *inner.first]
|
|
117
|
+
end
|
|
118
|
+
private_class_method :nested_shape
|
|
119
|
+
|
|
120
|
+
# :int64 if every value is an Integer, :float64 for Integers and Floats,
|
|
121
|
+
# :cmplx128 when Complex values join them, otherwise nil (keep object).
|
|
122
|
+
# Other Numerics (Rational, BigDecimal) stay object: a float column would
|
|
123
|
+
# round them, and a Complex one has room for them only as floats too.
|
|
91
124
|
def self.numeric_leaf_type(values)
|
|
92
125
|
if values.all? { |v| v.is_a?(Integer) }
|
|
93
126
|
:int64
|
|
94
|
-
elsif values.all? { |v| v.is_a?(
|
|
127
|
+
elsif values.all? { |v| v.is_a?(Integer) || v.is_a?(Float) }
|
|
95
128
|
:float64
|
|
129
|
+
elsif values.all? { |v| v.is_a?(Integer) || v.is_a?(Float) || v.is_a?(Complex) }
|
|
130
|
+
:cmplx128
|
|
96
131
|
end
|
|
97
132
|
end
|
|
98
133
|
private_class_method :numeric_leaf_type
|