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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CAFrame
|
|
4
|
+
|
|
5
|
+
# The one rule of a frame-wide verb over the frame's columns -- the group
|
|
6
|
+
# reductions and transforms, the windowed reductions -- kept in one place
|
|
7
|
+
# so that every verb answers in the same shape:
|
|
8
|
+
#
|
|
9
|
+
# - given names, the CArray (one) or the Array of them (several), as
|
|
10
|
+
# +df[...]+ returns columns; a named column whose type has no such
|
|
11
|
+
# operation is refused with CArray::DataTypeError, as the core refuses
|
|
12
|
+
# it, since it was asked for;
|
|
13
|
+
# - given none, every column whose type has it, the key columns left out,
|
|
14
|
+
# as a Hash of name to result that the caller makes a frame of.
|
|
15
|
+
#
|
|
16
|
+
# Whether a type has the operation is asked of the core on two copies of
|
|
17
|
+
# the column's first row (two blank cells of its kind when it has no
|
|
18
|
+
# rows), so the answer is about the type and not the data: a refusal
|
|
19
|
+
# there means the operation does not apply, while an error in the verb
|
|
20
|
+
# itself is left to raise. An N-D column (each row a profile) is asked,
|
|
21
|
+
# and computed, along the rows.
|
|
22
|
+
module ColumnVerbs
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def over_named_columns (verb, names, key_names, args = [], keywords = {})
|
|
27
|
+
if names.empty?
|
|
28
|
+
cols = {}
|
|
29
|
+
@frame.column_names.each do |name|
|
|
30
|
+
next if key_names.include?(name)
|
|
31
|
+
col = @frame[name]
|
|
32
|
+
next unless column_has?(col, verb, args, keywords)
|
|
33
|
+
cols[name] = yield(col)
|
|
34
|
+
end
|
|
35
|
+
return cols
|
|
36
|
+
end
|
|
37
|
+
results = names.map do |name|
|
|
38
|
+
col = @frame[@frame.__send__(:column_key, name, verb)]
|
|
39
|
+
unless column_has?(col, verb, args, keywords)
|
|
40
|
+
raise CArray::DataTypeError,
|
|
41
|
+
"#{verb}: column #{name.inspect} " \
|
|
42
|
+
"(#{CArray::Inspector.type_label(col)}) has no #{verb}"
|
|
43
|
+
end
|
|
44
|
+
yield(col)
|
|
45
|
+
end
|
|
46
|
+
results.size == 1 ? results.first : results
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# count with no value counts the present cells, which every type has;
|
|
50
|
+
# a Face's own count takes a value (CACategorical#count(label)), so the
|
|
51
|
+
# type is asked about count_not_masked instead.
|
|
52
|
+
def column_has? (col, verb, args, keywords)
|
|
53
|
+
verb = :count_not_masked if verb == :count && args.empty?
|
|
54
|
+
# Two cells: one cell does not reach what combines two (a String's
|
|
55
|
+
# median interpolates, its sum adds), and an empty column answers
|
|
56
|
+
# with the identity without asking the type at all.
|
|
57
|
+
tail = [nil] * (col.ndim - 1)
|
|
58
|
+
probe = if col.shape[0] > 0
|
|
59
|
+
col[CA_INT64([0, 0]), *tail]
|
|
60
|
+
else
|
|
61
|
+
col.resize(2, *col.shape[1..])
|
|
62
|
+
end
|
|
63
|
+
keywords = keywords.merge(axis: 0) if col.ndim > 1
|
|
64
|
+
probe.public_send(verb, *args, **keywords)
|
|
65
|
+
true
|
|
66
|
+
rescue CArray::DataTypeError, TypeError, ArgumentError
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/carray/frame/concat.rb
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
#
|
|
10
10
|
# Both are class methods (symmetric N-ary; no frame is privileged), like
|
|
11
11
|
# +CArray.meld+ / +CArray.concatenate+, not instance verbs.
|
|
12
|
+
#
|
|
13
|
+
# +CAFrame.stack+ and +CAFrame#unstack+ add and take away a layer axis
|
|
14
|
+
# instead of rows.
|
|
12
15
|
|
|
13
16
|
class CAFrame
|
|
14
17
|
# Weld frames along the row axis, view-style. Each output column is
|
|
@@ -28,6 +31,13 @@ class CAFrame
|
|
|
28
31
|
#
|
|
29
32
|
# The index is welded too when every frame has one (their +axis_name+
|
|
30
33
|
# must agree); if none do, the result has no index; a mix raises.
|
|
34
|
+
#
|
|
35
|
+
# A frame with no rows adds no values, so it takes no part in the result
|
|
36
|
+
# past these checks: its column set, its N-D columns' trailing dimensions
|
|
37
|
+
# and its index still have to agree, but the
|
|
38
|
+
# data types are the other frames' (a header-only CSV reads as object
|
|
39
|
+
# columns, which would otherwise refuse or demote the rest). When every
|
|
40
|
+
# frame is empty, the first one stands for them.
|
|
31
41
|
# Column-set mismatch raises — a union-with-UNDEF mode is a possible
|
|
32
42
|
# future opt-in, kept out here to stay explicit (memo §4.2).
|
|
33
43
|
#
|
|
@@ -37,8 +47,11 @@ class CAFrame
|
|
|
37
47
|
frames = frames.flatten
|
|
38
48
|
check_concat_inputs(frames, verb: "meld")
|
|
39
49
|
first = frames.first
|
|
40
|
-
names = first.
|
|
50
|
+
names = first.column_names
|
|
41
51
|
check_column_sets(frames, names, verb: "meld")
|
|
52
|
+
index_pieces(frames, verb: "meld")
|
|
53
|
+
check_trailing_shapes(frames, names, verb: "meld")
|
|
54
|
+
frames = frames_with_rows(frames)
|
|
42
55
|
cols = {}
|
|
43
56
|
names.each do |name|
|
|
44
57
|
cols[name] = CArray.meld(frames.map { |f| f[name] }, axis: 0)
|
|
@@ -54,8 +67,9 @@ class CAFrame
|
|
|
54
67
|
# For a view frame that shares storage with the inputs (strict same data type
|
|
55
68
|
# per column, chain composability preserved) use {CAFrame.meld}.
|
|
56
69
|
#
|
|
57
|
-
# Column matching, index handling,
|
|
58
|
-
# match {CAFrame.meld}
|
|
70
|
+
# Column matching, index handling, column-set / index-mix rules and the
|
|
71
|
+
# treatment of a frame with no rows match {CAFrame.meld}: an empty frame's
|
|
72
|
+
# data types take no part in the common type.
|
|
59
73
|
#
|
|
60
74
|
# CAFrame.concatenate(jan, feb, mar) # eager, independent result
|
|
61
75
|
# CAFrame.concatenate([jan, feb, mar]) # an Array is accepted too
|
|
@@ -63,8 +77,11 @@ class CAFrame
|
|
|
63
77
|
frames = frames.flatten
|
|
64
78
|
check_concat_inputs(frames, verb: "concatenate")
|
|
65
79
|
first = frames.first
|
|
66
|
-
names = first.
|
|
80
|
+
names = first.column_names
|
|
67
81
|
check_column_sets(frames, names, verb: "concatenate")
|
|
82
|
+
index_pieces(frames, verb: "concatenate")
|
|
83
|
+
check_trailing_shapes(frames, names, verb: "concatenate")
|
|
84
|
+
frames = frames_with_rows(frames)
|
|
68
85
|
cols = {}
|
|
69
86
|
names.each do |name|
|
|
70
87
|
cols[name] = CArray.concatenate(frames.map { |f| f[name] })
|
|
@@ -72,6 +89,108 @@ class CAFrame
|
|
|
72
89
|
new(cols, axis_name: first.axis_name, index: concatenate_index(frames))
|
|
73
90
|
end
|
|
74
91
|
|
|
92
|
+
# Stack frames of the same shape as layers. Each output column is
|
|
93
|
+
# +CArray.stack+ of that column across the frames along +axis+, counted in
|
|
94
|
+
# the column's own axes (0 is the row axis, so +axis+ starts at 1): a
|
|
95
|
+
# scalar column of K frames becomes a column of shape (nrow, K). The rows
|
|
96
|
+
# stay as they are, so row verbs work on the stack unchanged and a layer is
|
|
97
|
+
# a column axis -- +s["temp"][nil, k]+ is frame k's column, and
|
|
98
|
+
# +s["temp"].mean(axis: 1)+ reduces across the layers.
|
|
99
|
+
#
|
|
100
|
+
# The result is a view frame: its columns are CAStack views over the
|
|
101
|
+
# frames' columns, and writes reach them. Call +copy+ for an independent
|
|
102
|
+
# frame. The layers carry no labels; keep them alongside (the dates of the
|
|
103
|
+
# files, say) as an array of your own.
|
|
104
|
+
#
|
|
105
|
+
# Every frame must have the same columns, the same number of rows and the
|
|
106
|
+
# same index (by value) under the same axis name, or none; the result
|
|
107
|
+
# takes the first frame's. +unstack+ is the inverse:
|
|
108
|
+
#
|
|
109
|
+
# s = CAFrame.stack(jan1, jan2, jan3) # temp:float64 -> temp:float64[3]
|
|
110
|
+
# CAFrame.stack(*s.unstack(axis: 1)) # the same frame again
|
|
111
|
+
def self.stack(*frames, axis: 1)
|
|
112
|
+
frames = frames.flatten
|
|
113
|
+
check_concat_inputs(frames, verb: "stack")
|
|
114
|
+
check_layer_axis(axis, "stack")
|
|
115
|
+
first = frames.first
|
|
116
|
+
names = first.column_names
|
|
117
|
+
check_column_sets(frames, names, verb: "stack")
|
|
118
|
+
frames.each_with_index do |f, i|
|
|
119
|
+
next if i.zero?
|
|
120
|
+
unless f.nrow == first.nrow
|
|
121
|
+
raise ArgumentError,
|
|
122
|
+
"stack: frame #{i} has #{f.nrow} rows, frame 0 has #{first.nrow}"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
indexes, = index_pieces(frames, verb: "stack")
|
|
126
|
+
if indexes && ! indexes.all? { |x| same_index_values?(x, indexes.first) }
|
|
127
|
+
raise ArgumentError, "stack: the frames' indexes differ; stacked rows have to be the same rows"
|
|
128
|
+
end
|
|
129
|
+
cols = {}
|
|
130
|
+
names.each do |name|
|
|
131
|
+
parts = frames.map { |f| f[name] }
|
|
132
|
+
if axis > parts.first.ndim
|
|
133
|
+
raise ArgumentError,
|
|
134
|
+
"stack: axis #{axis} out of range for column #{name.inspect} " \
|
|
135
|
+
"(a #{parts.first.ndim}-D column takes 1 to #{parts.first.ndim})"
|
|
136
|
+
end
|
|
137
|
+
cols[name] = CArray.stack(parts, axis: axis)
|
|
138
|
+
end
|
|
139
|
+
new(cols, axis_name: first.axis_name, index: first.index)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Split the frame along a column axis into an Array of frames, one per
|
|
143
|
+
# position on that axis -- the inverse of {CAFrame.stack}. +axis+ counts
|
|
144
|
+
# the columns' own axes (0 is the row axis, so it starts at 1). Every
|
|
145
|
+
# column must have that axis, and the same length along it: a column
|
|
146
|
+
# without it has no layer to give each frame. Each frame's columns are
|
|
147
|
+
# views of this frame's, and each has this frame's index.
|
|
148
|
+
#
|
|
149
|
+
# s.unstack(axis: 1) # => [frame of layer 0, frame of layer 1, ...]
|
|
150
|
+
def unstack(axis:)
|
|
151
|
+
self.class.send(:check_layer_axis, axis, "unstack")
|
|
152
|
+
if @columns.empty?
|
|
153
|
+
raise ArgumentError, "unstack: the frame has no columns to unstack"
|
|
154
|
+
end
|
|
155
|
+
length = nil
|
|
156
|
+
@columns.each do |name, col|
|
|
157
|
+
unless axis < col.ndim
|
|
158
|
+
raise ArgumentError,
|
|
159
|
+
"unstack: column #{name.inspect} is #{col.ndim}-D and has no axis #{axis}"
|
|
160
|
+
end
|
|
161
|
+
length ||= col.shape[axis]
|
|
162
|
+
unless col.shape[axis] == length
|
|
163
|
+
raise ArgumentError,
|
|
164
|
+
"unstack: column #{name.inspect} has #{col.shape[axis]} along axis #{axis}, " \
|
|
165
|
+
"the others #{length}"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
(0...length).map do |k|
|
|
169
|
+
cols = @columns.to_h do |name, col|
|
|
170
|
+
idx = [nil] * col.ndim
|
|
171
|
+
idx[axis] = k
|
|
172
|
+
[name, col[*idx]]
|
|
173
|
+
end
|
|
174
|
+
CAFrame.new(cols, axis_name: @axis_name, index: @index)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def self.check_layer_axis(axis, verb)
|
|
179
|
+
unless axis.is_a?(Integer)
|
|
180
|
+
raise TypeError, "#{verb}: axis must be an Integer (got #{axis.inspect})"
|
|
181
|
+
end
|
|
182
|
+
if axis == 0
|
|
183
|
+
raise ArgumentError,
|
|
184
|
+
"#{verb}: axis 0 is the row axis; rows are joined by meld or concatenate"
|
|
185
|
+
end
|
|
186
|
+
if axis < 0
|
|
187
|
+
raise ArgumentError,
|
|
188
|
+
"#{verb}: axis counts a column's axes from 1 (got #{axis}); " \
|
|
189
|
+
"a negative axis would mean a different axis in columns of different ndim"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
private_class_method :check_layer_axis
|
|
193
|
+
|
|
75
194
|
# ---- shared validators -------------------------------------------------
|
|
76
195
|
|
|
77
196
|
def self.check_concat_inputs(frames, verb:)
|
|
@@ -86,15 +205,47 @@ class CAFrame
|
|
|
86
205
|
expected = names.sort
|
|
87
206
|
frames.each_with_index do |f, i|
|
|
88
207
|
next if i.zero?
|
|
89
|
-
if f.
|
|
208
|
+
if f.column_names.sort != expected
|
|
90
209
|
raise ArgumentError,
|
|
91
|
-
"#{verb}: frame #{i} has columns #{f.
|
|
210
|
+
"#{verb}: frame #{i} has columns #{f.column_names.inspect}, " \
|
|
92
211
|
"expected the same set as #{names.inspect}"
|
|
93
212
|
end
|
|
94
213
|
end
|
|
95
214
|
end
|
|
96
215
|
private_class_method :check_column_sets
|
|
97
216
|
|
|
217
|
+
# A column's trailing dimensions are its shape, not a type the other
|
|
218
|
+
# frames can settle, so a frame with no rows has to agree on them too.
|
|
219
|
+
def self.check_trailing_shapes(frames, names, verb:)
|
|
220
|
+
names.each do |name|
|
|
221
|
+
trailing = frames.first[name].shape[1..]
|
|
222
|
+
frames.each_with_index do |f, i|
|
|
223
|
+
next if i.zero? || f[name].shape[1..] == trailing
|
|
224
|
+
raise ArgumentError,
|
|
225
|
+
"#{verb}: column #{name.inspect} has trailing dimensions " \
|
|
226
|
+
"#{f[name].shape[1..].inspect} in frame #{i}, #{trailing.inspect} in frame 0"
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
private_class_method :check_trailing_shapes
|
|
231
|
+
|
|
232
|
+
# Two indexes hold the same rows when their values agree cell by cell; a
|
|
233
|
+
# NaN label matches a NaN label, and a masked cell a masked cell.
|
|
234
|
+
def self.same_index_values?(a, b)
|
|
235
|
+
return false unless a.shape == b.shape
|
|
236
|
+
a.to_a.zip(b.to_a).all? do |x, y|
|
|
237
|
+
x.equal?(y) || x == y || (x.is_a?(Float) && y.is_a?(Float) && x.nan? && y.nan?)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
private_class_method :same_index_values?
|
|
241
|
+
|
|
242
|
+
# The frames that add rows, or the first frame when none does.
|
|
243
|
+
def self.frames_with_rows(frames)
|
|
244
|
+
with_rows = frames.reject { |f| f.nrow.zero? }
|
|
245
|
+
with_rows.empty? ? [frames.first] : with_rows
|
|
246
|
+
end
|
|
247
|
+
private_class_method :frames_with_rows
|
|
248
|
+
|
|
98
249
|
# ---- index helpers -----------------------------------------------------
|
|
99
250
|
|
|
100
251
|
# Index welded via CArray.meld (view). Every frame must agree: all
|
data/lib/carray/frame/convert.rb
CHANGED
|
@@ -16,21 +16,25 @@ class CAFrame
|
|
|
16
16
|
# column name => value; a scalar cell is a Ruby value, an N-D cell is a Ruby
|
|
17
17
|
# Array, and a masked cell (UNDEF) is +nil+. Normalizing UNDEF -> nil and
|
|
18
18
|
# CArray -> Array (unlike +each_row+, which yields the raw view with UNDEF /
|
|
19
|
-
# CArray slices)
|
|
20
|
-
#
|
|
19
|
+
# CArray slices) makes the result JSON-serializable and lets
|
|
20
|
+
# +from_records(df.to_records)+ give back the column names, the values and
|
|
21
|
+
# the mask. The rest is not in a record: +from_records+ rebuilds each type
|
|
22
|
+
# from the values (an int32 comes back int64, a time column as an object
|
|
23
|
+
# column of its elements), the index comes back as an ordinary first
|
|
24
|
+
# column, and a frame with no rows gives +[]+, which has no column names.
|
|
21
25
|
def to_records
|
|
22
26
|
each_row.map { |row| row.transform_values { |v| record_value(v) } }
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
# Hand the frame over as a CArray with the minimum work (memo §11.9): a
|
|
26
|
-
# 2-D **view** of shape (nrow,
|
|
30
|
+
# 2-D **view** of shape (nrow, ncol), one per column in column
|
|
27
31
|
# order (§12-C). Nothing is materialised — the result is a +CAStack+ over
|
|
28
32
|
# the stored columns, so reads gather from them and writes flow back
|
|
29
33
|
# (§3.6). Call +copy+ on it for an independent, owned matrix; +CArray.tabulate+
|
|
30
34
|
# is the eager sibling that builds one directly.
|
|
31
35
|
#
|
|
32
36
|
# Only same-shape scalar (1-D) columns qualify. An N-D column has no single
|
|
33
|
-
# matrix form and raises —
|
|
37
|
+
# matrix form and raises — take it per column with +df["name"]+. A mixed
|
|
34
38
|
# data type set is promoted to a common type (+result_type+, §12-F) through
|
|
35
39
|
# lazy cast lanes, so the promotion costs no buffer either.
|
|
36
40
|
#
|
|
@@ -48,12 +52,12 @@ class CAFrame
|
|
|
48
52
|
if nd
|
|
49
53
|
raise ArgumentError,
|
|
50
54
|
"to_ca needs all-scalar (1-D) columns; #{nd.first.inspect} is " \
|
|
51
|
-
"#{nd.last.ndim}-D —
|
|
55
|
+
"#{nd.last.ndim}-D — take it per column with df[name]"
|
|
52
56
|
end
|
|
53
57
|
refuse_unshared_columns(cols) if writable
|
|
54
58
|
begin
|
|
55
59
|
CArray.stack(cols, axis: 1)
|
|
56
|
-
rescue ArgumentError,
|
|
60
|
+
rescue ArgumentError, TypeError, CArray::DataTypeError => e
|
|
57
61
|
# The columns have no common type (a text or Face column beside a
|
|
58
62
|
# numeric one). Point at the frame-level verb that gives them one.
|
|
59
63
|
raise e.class,
|
|
@@ -87,9 +91,80 @@ class CAFrame
|
|
|
87
91
|
if UNDEF.equal?(v)
|
|
88
92
|
nil
|
|
89
93
|
elsif v.is_a?(CArray)
|
|
90
|
-
v.to_a
|
|
94
|
+
record_value(v.to_a)
|
|
95
|
+
elsif v.is_a?(Array)
|
|
96
|
+
v.map { |e| record_value(e) }
|
|
91
97
|
else
|
|
92
98
|
v
|
|
93
99
|
end
|
|
94
100
|
end
|
|
95
101
|
end
|
|
102
|
+
|
|
103
|
+
class CArray
|
|
104
|
+
# @overload to_frame(*names, axis: -1)
|
|
105
|
+
# Shows this array as a table: a CAFrame whose rows run along axis 0 and
|
|
106
|
+
# whose columns are the positions along `axis`. Column `j` is the view of
|
|
107
|
+
# `self` with `j` at `axis` and every other axis whole, so its shape is
|
|
108
|
+
# `self`'s shape without `axis`. A 2-D array gives 1-D columns, an array
|
|
109
|
+
# of more dimensions gives N-D columns:
|
|
110
|
+
#
|
|
111
|
+
# ```
|
|
112
|
+
# a = CArray.float64(3, 2).seq!
|
|
113
|
+
# df = a.to_frame("x", "y")
|
|
114
|
+
# df["y"] # => a[nil, 1], shape [3]
|
|
115
|
+
#
|
|
116
|
+
# img = CArray.uint8(480, 640, 3)
|
|
117
|
+
# img.to_frame("r", "g", "b")["r"].shape # => [480, 640]
|
|
118
|
+
# ```
|
|
119
|
+
#
|
|
120
|
+
# Nothing is copied: each column is a view, so writes through the frame
|
|
121
|
+
# reach `self` and writes to `self` show in the frame, values and mask
|
|
122
|
+
# alike. Use `copy` on the frame for an independent one.
|
|
123
|
+
#
|
|
124
|
+
# Without names the columns are called `"c0"`, `"c1"`, ... A 1-D array
|
|
125
|
+
# has no axis to take columns from; give it one with `a[nil, :_]`.
|
|
126
|
+
#
|
|
127
|
+
# @param names [Array<String>] column names, one per position along `axis`.
|
|
128
|
+
# @param axis [Integer] the axis the columns are taken along; it cannot
|
|
129
|
+
# be axis 0, which holds the rows.
|
|
130
|
+
# @return [CAFrame]
|
|
131
|
+
# @raise [TypeError] when a name is not a String or `axis` is not an
|
|
132
|
+
# Integer.
|
|
133
|
+
# @raise [ArgumentError] when `axis` is out of range or is the row axis,
|
|
134
|
+
# when the number of names differs from the length of `axis`, or when
|
|
135
|
+
# a name repeats.
|
|
136
|
+
def to_frame (*names, axis: -1)
|
|
137
|
+
axis = -1 if axis.nil?
|
|
138
|
+
if ndim < 2
|
|
139
|
+
raise ArgumentError,
|
|
140
|
+
"to_frame: a #{ndim}-D array has no axis to take columns from " \
|
|
141
|
+
"besides the row axis (axis 0); give it one with a[nil, :_]"
|
|
142
|
+
end
|
|
143
|
+
ax = normalize_axis(axis, "to_frame")
|
|
144
|
+
if ax == 0
|
|
145
|
+
raise ArgumentError,
|
|
146
|
+
"to_frame: axis #{axis} is the row axis (axis 0); columns are " \
|
|
147
|
+
"taken along another axis"
|
|
148
|
+
end
|
|
149
|
+
ncol = shape[ax]
|
|
150
|
+
if names.empty?
|
|
151
|
+
names = Array.new(ncol) { |j| "c#{j}" }
|
|
152
|
+
else
|
|
153
|
+
names.each { |name| CAFrame.__send__(:column_key, name, "to_frame") }
|
|
154
|
+
raise ArgumentError, "to_frame: a column name can not be empty" if names.include?("")
|
|
155
|
+
unless names.size == ncol
|
|
156
|
+
raise ArgumentError,
|
|
157
|
+
"to_frame: #{names.size} names for #{ncol} columns " \
|
|
158
|
+
"(axis #{axis} has length #{ncol})"
|
|
159
|
+
end
|
|
160
|
+
CAFrame.__send__(:refuse_repeated_names, names, "to_frame")
|
|
161
|
+
end
|
|
162
|
+
index = Array.new(ndim, nil)
|
|
163
|
+
columns = {}
|
|
164
|
+
names.each_with_index do |name, j|
|
|
165
|
+
index[ax] = j
|
|
166
|
+
columns[name] = self[*index]
|
|
167
|
+
end
|
|
168
|
+
CAFrame.new(columns)
|
|
169
|
+
end
|
|
170
|
+
end
|