carray 3.0.3 → 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 +264 -0
- data/README.md +3 -2
- data/carray.gemspec +1 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +1 -6
- data/ext/ca_axis_group.c +27 -27
- data/ext/ca_binop_dispatch.c +1 -29
- data/ext/ca_binop_dispatch.h +0 -1
- data/ext/ca_categorical_iterator.c +205 -134
- data/ext/ca_compare.c +7 -18
- data/ext/ca_group_iter.c +16 -17
- data/ext/ca_kernel_iterator.c +1234 -1994
- data/ext/ca_kernel_iterator.h +391 -437
- data/ext/ca_monop_dispatch.c +82 -59
- data/ext/ca_monop_dispatch.h +1 -0
- data/ext/ca_obj_array.c +96 -39
- data/ext/ca_obj_bincmp.c +24 -21
- data/ext/ca_obj_binop.c +15 -26
- data/ext/ca_obj_bitarray.c +3 -2
- data/ext/ca_obj_bitfield.c +5 -6
- data/ext/ca_obj_block.c +18 -9
- data/ext/ca_obj_byte_swap.c +5 -6
- data/ext/ca_obj_const_string.c +173 -19
- data/ext/ca_obj_face.c +238 -57
- data/ext/ca_obj_face.h +19 -0
- data/ext/ca_obj_fake.c +15 -29
- data/ext/ca_obj_farray.c +6 -3
- data/ext/ca_obj_field.c +17 -5
- data/ext/ca_obj_fixlen_string.c +2 -3
- data/ext/ca_obj_grid.c +406 -132
- data/ext/ca_obj_meld.c +172 -34
- data/ext/ca_obj_moncmp.c +23 -8
- data/ext/ca_obj_monop.c +67 -57
- data/ext/ca_obj_object.c +157 -51
- data/ext/ca_obj_record.c +12 -7
- data/ext/ca_obj_reduce.c +13 -8
- data/ext/ca_obj_refer.c +25 -27
- data/ext/ca_obj_remap.c +30 -9
- data/ext/ca_obj_repeat.c +9 -10
- data/ext/ca_obj_roll.c +6 -5
- data/ext/ca_obj_select.c +14 -9
- data/ext/ca_obj_select_axis.c +15 -47
- data/ext/ca_obj_shift.c +11 -1
- data/ext/ca_obj_stack.c +179 -34
- data/ext/ca_obj_stride.c +162 -115
- data/ext/ca_obj_string.c +71 -4
- data/ext/ca_obj_tile.c +6 -5
- data/ext/ca_obj_time.c +22 -6
- data/ext/ca_obj_timedelta.c +22 -6
- data/ext/ca_obj_transpose.c +5 -3
- data/ext/ca_obj_triop.c +23 -32
- data/ext/ca_obj_window.c +190 -26
- data/ext/ca_sweep_engine.c +2 -2
- data/ext/caframe_csv_reader.c +80 -1
- data/ext/carray.h +181 -75
- data/ext/carray_access.c +184 -96
- data/ext/carray_address_basis.c +343 -87
- data/ext/carray_attribute.c +89 -6
- data/ext/carray_bincount.c +10 -8
- data/ext/carray_broadcast.c +13 -8
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +108 -0
- data/ext/carray_cast.c +142 -24
- data/ext/carray_cast_func.rb +46 -6
- data/ext/carray_class.c +4 -3
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +16 -7
- data/ext/carray_copy.c +51 -14
- data/ext/carray_core.c +147 -55
- data/ext/carray_count.c +50 -20
- data/ext/carray_element.c +76 -33
- data/ext/carray_factorize.c +118 -64
- data/ext/carray_generate.c +191 -50
- data/ext/carray_histogram.c +62 -17
- data/ext/carray_hold.c +12 -15
- data/ext/carray_index_classifier.c +388 -176
- data/ext/carray_internal.h +336 -29
- data/ext/carray_kernels_bincmp.c +4453 -0
- data/ext/carray_kernels_binop.c +11368 -0
- data/ext/carray_kernels_init.c +1734 -0
- data/ext/carray_kernels_moncmp.c +2104 -0
- data/ext/carray_kernels_monop.c +18369 -0
- data/ext/carray_kernels_reduce_aggregate.c +23818 -0
- data/ext/carray_kernels_reduce_boolean.c +406 -0
- data/ext/carray_kernels_reduce_cumulative.c +10086 -0
- data/ext/carray_kernels_reduce_extreme.c +17634 -0
- data/ext/carray_kernels_reduce_variance.c +3849 -0
- data/ext/carray_kernels_scan.c +4302 -0
- data/ext/carray_kernels_search.c +33829 -0
- data/ext/carray_kernels_sort.c +10906 -0
- data/ext/carray_kernels_triop.c +1864 -0
- data/ext/carray_key_windows.c +216 -0
- data/ext/carray_lazy.c +170 -111
- data/ext/carray_mask.c +292 -72
- data/ext/carray_median_percentile.c +82 -28
- data/ext/carray_memory_view.c +669 -561
- data/ext/carray_operator.c +124 -47
- data/ext/carray_order.c +384 -76
- data/ext/carray_partition.c +28 -27
- data/ext/carray_random.c +67 -13
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +63 -38
- data/ext/carray_segment.c +3 -3
- data/ext/carray_slab.c +91 -28
- data/ext/carray_sort.c +35 -35
- data/ext/carray_struct.c +19 -18
- data/ext/carray_test.c +26 -5
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +246 -45
- data/ext/mk_call_cfunc.rb +19 -2
- data/ext/mkkernel.rb +198 -240
- data/ext/ruby_carray.c +10 -4
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +126 -22
- data/lib/carray/autoload_carray.rb +8 -31
- data/lib/carray/autoload_method_extension.rb +14 -5
- data/lib/carray/axis_group.rb +255 -106
- data/lib/carray/basics.rb +78 -10
- data/lib/carray/bincount_nd.rb +12 -2
- data/lib/carray/block_iterator.rb +126 -23
- data/lib/carray/boolean_reduce.rb +17 -17
- data/lib/carray/categorical.rb +165 -43
- data/lib/carray/categorical_iterator.rb +130 -27
- data/lib/carray/complex.rb +5 -5
- data/lib/carray/conditional.rb +67 -18
- data/lib/carray/const_string.rb +14 -5
- 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/convert.rb +69 -0
- data/lib/carray/frame/csv_parser.rb +111 -17
- data/lib/carray/frame/describe.rb +3 -2
- data/lib/carray/frame/frame.rb +246 -21
- data/lib/carray/frame/group.rb +510 -95
- data/lib/carray/frame/io.rb +115 -28
- data/lib/carray/frame/join.rb +312 -51
- data/lib/carray/frame/records.rb +83 -7
- data/lib/carray/frame/reshape.rb +42 -35
- data/lib/carray/frame/sort.rb +7 -19
- data/lib/carray/frame/verbs.rb +188 -48
- data/lib/carray/frame/windows.rb +117 -0
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +11 -4
- data/lib/carray/fusion.rb +40 -16
- data/lib/carray/histogram.rb +6 -1
- data/lib/carray/inspect.rb +31 -8
- data/lib/carray/iterator.rb +85 -7
- data/lib/carray/key_windows.rb +620 -0
- data/lib/carray/lazy.rb +182 -183
- data/lib/carray/mask_gap_fill.rb +63 -57
- data/lib/carray/meld_reduce.rb +12 -14
- data/lib/carray/methods/bin.rb +26 -18
- data/lib/carray/methods/bincount.rb +16 -0
- data/lib/carray/methods/bit_string.rb +1 -1
- data/lib/carray/methods/choose.rb +16 -3
- data/lib/carray/methods/composition.rb +3 -1
- data/lib/carray/methods/gather_nd.rb +14 -5
- data/lib/carray/methods/insert_block.rb +2 -2
- data/lib/carray/methods/is_between.rb +59 -0
- data/lib/carray/methods/is_in.rb +53 -36
- data/lib/carray/methods/join.rb +1 -6
- data/lib/carray/methods/lag.rb +49 -0
- data/lib/carray/methods/locate_addr.rb +47 -51
- data/lib/carray/methods/mode.rb +1 -1
- data/lib/carray/methods/nunique.rb +11 -6
- data/lib/carray/methods/resize.rb +3 -3
- data/lib/carray/methods/value_counts.rb +4 -11
- data/lib/carray/record_order.rb +3 -3
- data/lib/carray/rng.rb +3 -0
- data/lib/carray/runtime.rb +53 -8
- data/lib/carray/segment_iterator.rb +313 -82
- data/lib/carray/serialize.rb +24 -10
- data/lib/carray/slab_iterator.rb +50 -0
- data/lib/carray/string.rb +105 -5
- data/lib/carray/string_operation_extension.rb +8 -1
- data/lib/carray/struct.rb +9 -21
- data/lib/carray/struct_builder.rb +19 -8
- data/lib/carray/time.rb +726 -104
- data/lib/carray/window_iterator.rb +122 -16
- data/lib/carray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +4 -0
- data/yard-stubs/ca_obj_grid.rb +21 -10
- data/yard-stubs/ca_obj_refer.rb +2 -1
- data/yard-stubs/ca_obj_roll.rb +2 -2
- data/yard-stubs/ca_obj_shift.rb +5 -1
- data/yard-stubs/ca_obj_tile.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +8 -3
- data/yard-stubs/carray_access.rb +6 -1
- data/yard-stubs/carray_attribute.rb +0 -5
- data/yard-stubs/carray_cast.rb +10 -0
- data/yard-stubs/carray_conversion.rb +2 -1
- data/yard-stubs/carray_copy.rb +4 -2
- data/yard-stubs/carray_count.rb +4 -4
- data/yard-stubs/carray_element.rb +10 -4
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_mask.rb +26 -2
- data/yard-stubs/carray_math.rb +27 -1
- data/yard-stubs/carray_median_percentile.rb +5 -2
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +29 -18
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +14 -9
- data/yard-stubs/carray_slab.rb +9 -2
- data/yard-stubs/carray_sort.rb +6 -5
- data/yard-stubs/carray_test.rb +5 -3
- metadata +25 -3
- data/lib/carray/arrow_tensor.rb +0 -401
- data/lib/carray/table.rb +0 -108
data/lib/carray/frame/frame.rb
CHANGED
|
@@ -37,10 +37,12 @@ class CAFrame
|
|
|
37
37
|
# Integer data_types that select rows positionally when used as a df[] key.
|
|
38
38
|
INTEGER_TYPES = [:int8, :int16, :int32, :int64,
|
|
39
39
|
:uint8, :uint16, :uint32, :uint64].freeze
|
|
40
|
+
Ractor.make_shareable(INTEGER_TYPES)
|
|
40
41
|
private_constant :INTEGER_TYPES
|
|
41
42
|
|
|
42
43
|
# @!visibility private
|
|
43
44
|
DEFAULT_AXIS_NAME = "row"
|
|
45
|
+
Ractor.make_shareable(DEFAULT_AXIS_NAME)
|
|
44
46
|
|
|
45
47
|
# Build a frame from a Hash of +name => column+. Columns may be CArrays or
|
|
46
48
|
# anything that answers +to_ca+ (Ruby Array, lazy view). All columns must
|
|
@@ -51,22 +53,34 @@ class CAFrame
|
|
|
51
53
|
# y)+). +:axis_name+ / +:index+ are control options; any remaining
|
|
52
54
|
# (string-keyed) options are treated as columns.
|
|
53
55
|
#
|
|
54
|
-
# Column names are
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
56
|
+
# Column names are Strings, in an explicit column hash as in inline pairs.
|
|
57
|
+
# A stray Symbol among the keywords is a mistyped control option
|
|
58
|
+
# (+:axis_name+ / +:index+), not a column, and is named as such.
|
|
59
|
+
#
|
|
60
|
+
# +types:+ casts the built frame as +from_records+ does: it takes anything
|
|
61
|
+
# +cast+ takes as a map (a name => type map, :infer, or a map with
|
|
62
|
+
# +default:+), with +on_error:+ as for +cast+. A cast column is a new array,
|
|
63
|
+
# so a CArray given for it is no longer shared with the frame.
|
|
58
64
|
def initialize(columns = {}, **opts)
|
|
59
65
|
axis_name = opts.delete(:axis_name)
|
|
60
66
|
index = opts.delete(:index)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
attrs = opts.delete(:attrs)
|
|
68
|
+
types = opts.delete(:types)
|
|
69
|
+
on_error = opts.key?(:on_error) ? opts.delete(:on_error) : :mask
|
|
70
|
+
opts.each_key do |k|
|
|
71
|
+
CAFrame.__send__(:column_key, k, "CAFrame.new",
|
|
72
|
+
hint: "axis_name:, index:, attrs:, types: and on_error: are the options")
|
|
73
|
+
end
|
|
74
|
+
@attrs = nil
|
|
75
|
+
unless attrs.nil?
|
|
76
|
+
raise TypeError, "CAFrame.new: attrs: takes a Hash (got #{attrs.class})" unless attrs.is_a?(Hash)
|
|
77
|
+
set_attrs(attrs)
|
|
65
78
|
end
|
|
66
79
|
columns = columns.merge(opts) unless opts.empty?
|
|
67
80
|
|
|
68
81
|
@columns = {}
|
|
69
|
-
@axis_name = axis_name
|
|
82
|
+
@axis_name = axis_name.nil? ? DEFAULT_AXIS_NAME :
|
|
83
|
+
CAFrame.__send__(:column_key, axis_name, "CAFrame.new", "axis_name:")
|
|
70
84
|
@index = nil
|
|
71
85
|
# The row axis name this frame had before set_index promoted a column over
|
|
72
86
|
# it, so reset_index can put it back. A frame built with an index never
|
|
@@ -77,7 +91,8 @@ class CAFrame
|
|
|
77
91
|
n = nil
|
|
78
92
|
n_from = nil
|
|
79
93
|
columns.each do |name, col|
|
|
80
|
-
key = name.
|
|
94
|
+
key = column_key(name, "CAFrame.new")
|
|
95
|
+
raise ArgumentError, "CAFrame.new: a column name can not be empty" if key.empty?
|
|
81
96
|
ca = coerce_column(col)
|
|
82
97
|
len = ca.shape[0]
|
|
83
98
|
if n.nil?
|
|
@@ -115,6 +130,8 @@ class CAFrame
|
|
|
115
130
|
raise ArgumentError,
|
|
116
131
|
"axis_name #{@axis_name.inspect} collides with a column of the same name"
|
|
117
132
|
end
|
|
133
|
+
|
|
134
|
+
cast(types, on_error: on_error, option_name: :types) unless types.nil?
|
|
118
135
|
end
|
|
119
136
|
|
|
120
137
|
# --- df[...] : key type decides the axis (memo §13.2) ------------------
|
|
@@ -251,7 +268,11 @@ class CAFrame
|
|
|
251
268
|
# first so a refused edit leaves the frame as it was: the edits change the
|
|
252
269
|
# column table before any instance variable.
|
|
253
270
|
private def refuse_if_frozen
|
|
254
|
-
|
|
271
|
+
return unless frozen?
|
|
272
|
+
# The size, not the table: inspect prints every row.
|
|
273
|
+
size = "#{@nrow} row#{@nrow == 1 ? "" : "s"}, #{ncol} column#{ncol == 1 ? "" : "s"}"
|
|
274
|
+
raise FrozenError.new("can't modify frozen CAFrame (#{size})",
|
|
275
|
+
receiver: self)
|
|
255
276
|
end
|
|
256
277
|
|
|
257
278
|
# Make this frame read-only and return it. It is +freeze+ extended to the
|
|
@@ -325,6 +346,7 @@ class CAFrame
|
|
|
325
346
|
# leaves the old one alone (the same rule +cast+ follows, for the physical
|
|
326
347
|
# reason that a type change cannot reuse the buffer).
|
|
327
348
|
private def rebind_column(key, column)
|
|
349
|
+
raise ArgumentError, "df[name] =: a column name can not be empty" if key.empty?
|
|
328
350
|
if @index && key == @axis_name
|
|
329
351
|
raise ArgumentError,
|
|
330
352
|
"#{key.inspect} names the index, not a column; " \
|
|
@@ -369,8 +391,7 @@ class CAFrame
|
|
|
369
391
|
def select(*names)
|
|
370
392
|
raise ArgumentError, "select requires at least one column name" if names.empty?
|
|
371
393
|
cols = {}
|
|
372
|
-
names.each do |
|
|
373
|
-
key = name.to_s
|
|
394
|
+
column_keys(names, "select").each do |key|
|
|
374
395
|
raise KeyError, "no column #{key.inspect}" unless @columns.key?(key)
|
|
375
396
|
cols[key] = @columns[key]
|
|
376
397
|
end
|
|
@@ -399,7 +420,7 @@ class CAFrame
|
|
|
399
420
|
mask = yield(self)
|
|
400
421
|
unless mask.is_a?(CArray) && mask.data_type == :boolean
|
|
401
422
|
raise ArgumentError,
|
|
402
|
-
"filter block must return a boolean CArray (got #{mask.class})"
|
|
423
|
+
"filter block must return a boolean CArray (got #{mask.is_a?(CArray) ? "a #{CArray::Inspector.type_label(mask)} CArray" : mask.class})"
|
|
403
424
|
end
|
|
404
425
|
unless mask.shape == [@nrow]
|
|
405
426
|
raise ArgumentError,
|
|
@@ -408,12 +429,69 @@ class CAFrame
|
|
|
408
429
|
select_rows(mask, keep_masked: keep_masked)
|
|
409
430
|
end
|
|
410
431
|
|
|
432
|
+
# Judge every row against named rules, and return the verdicts as a frame:
|
|
433
|
+
#
|
|
434
|
+
# report = df.verify(
|
|
435
|
+
# "age is not negative" => ->(f) { f["age"] >= 0 },
|
|
436
|
+
# "end after start" => ->(f) { f["end"] > f["start"] },
|
|
437
|
+
# )
|
|
438
|
+
# df.filter { |f| report["end after start"].not } # the rows that break it
|
|
439
|
+
#
|
|
440
|
+
# A rule is called with the frame, as a +filter+ block is, and returns a
|
|
441
|
+
# boolean CArray with one entry per row on its first axis; true means the
|
|
442
|
+
# rule holds. A rule over an N-D column may answer per cell, in the
|
|
443
|
+
# column's own shape, so the cells that break it can be masked directly.
|
|
444
|
+
# A cell the rule could not judge -- it read a masked value -- stays
|
|
445
|
+
# UNDEF rather than becoming false.
|
|
446
|
+
#
|
|
447
|
+
# The result has one column per rule, named after it, and this frame's
|
|
448
|
+
# index and row axis name.
|
|
449
|
+
#
|
|
450
|
+
# @param rules [Hash{String => #call}] the rules by name.
|
|
451
|
+
# @return [CAFrame]
|
|
452
|
+
# @raise [ArgumentError] when there are no rules, a name is empty, or a
|
|
453
|
+
# rule does not return a boolean CArray of this frame's length.
|
|
454
|
+
# @raise [TypeError] when a name is not a String.
|
|
455
|
+
def verify(rules)
|
|
456
|
+
unless rules.is_a?(Hash)
|
|
457
|
+
raise ArgumentError, "verify: the rules are a Hash of name => rule (got #{rules.class})"
|
|
458
|
+
end
|
|
459
|
+
raise ArgumentError, "verify: there are no rules" if rules.empty?
|
|
460
|
+
verdicts = {}
|
|
461
|
+
rules.each do |name, rule|
|
|
462
|
+
key = column_name_arg(name, "verify")
|
|
463
|
+
if @index && key == @axis_name
|
|
464
|
+
raise ArgumentError,
|
|
465
|
+
"verify: rule #{key.inspect} has the name of the row axis, " \
|
|
466
|
+
"which the index has in the result; name the rule otherwise"
|
|
467
|
+
end
|
|
468
|
+
unless rule.respond_to?(:call)
|
|
469
|
+
raise ArgumentError, "verify: rule #{key.inspect} is not callable (got #{rule.class})"
|
|
470
|
+
end
|
|
471
|
+
verdict = rule.call(self)
|
|
472
|
+
unless verdict.is_a?(CArray) && verdict.data_type == :boolean
|
|
473
|
+
raise ArgumentError,
|
|
474
|
+
"verify: rule #{key.inspect} must return a boolean CArray (got " \
|
|
475
|
+
"#{verdict.is_a?(CArray) ? "a #{verdict.data_type} CArray" : verdict.class})"
|
|
476
|
+
end
|
|
477
|
+
unless verdict.shape[0] == @nrow
|
|
478
|
+
raise ArgumentError,
|
|
479
|
+
"verify: rule #{key.inspect} returned shape #{verdict.shape.inspect}, " \
|
|
480
|
+
"expected #{@nrow} rows on the first axis"
|
|
481
|
+
end
|
|
482
|
+
verdicts[key] = verdict
|
|
483
|
+
end
|
|
484
|
+
# The verdicts are a table about the rows, not a part of this one, so the
|
|
485
|
+
# frame's attributes are not carried.
|
|
486
|
+
CAFrame.new(verdicts, axis_name: @axis_name, index: @index)
|
|
487
|
+
end
|
|
488
|
+
|
|
411
489
|
# Move a column to the index and name the row axis after it (memo §3).
|
|
412
490
|
# An index-role change: the data is unchanged, so this mutates self and
|
|
413
491
|
# returns it (memo §3.8). Any existing index is replaced.
|
|
414
492
|
def set_index(name)
|
|
415
493
|
refuse_if_frozen
|
|
416
|
-
key = name
|
|
494
|
+
key = column_key(name, "set_index")
|
|
417
495
|
raise KeyError, "no column #{key.inspect}" unless @columns.key?(key)
|
|
418
496
|
idx = @columns[key]
|
|
419
497
|
unless idx.ndim == 1
|
|
@@ -517,7 +595,8 @@ class CAFrame
|
|
|
517
595
|
def copy
|
|
518
596
|
cols = {}
|
|
519
597
|
@columns.each { |k, v| cols[k] = v.copy }
|
|
520
|
-
CAFrame.new(cols, axis_name: @axis_name, index: @index && @index.copy
|
|
598
|
+
CAFrame.new(cols, axis_name: @axis_name, index: @index && @index.copy,
|
|
599
|
+
attrs: @attrs)
|
|
521
600
|
end
|
|
522
601
|
|
|
523
602
|
# +dup+ / +clone+ share the column data -- that is the CArray dup contract
|
|
@@ -528,6 +607,84 @@ class CAFrame
|
|
|
528
607
|
private def initialize_copy(other)
|
|
529
608
|
super
|
|
530
609
|
@columns = @columns.dup
|
|
610
|
+
@attrs = @attrs.dup if @attrs
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
# Marshal writes the instance variables as they are. Loading stores the
|
|
614
|
+
# attribute values again as set_attr does, as frozen copies: Marshal
|
|
615
|
+
# brings back a value thawed.
|
|
616
|
+
private def marshal_dump
|
|
617
|
+
instance_variables.to_h { |name| [name, instance_variable_get(name)] }
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
private def marshal_load(ivars)
|
|
621
|
+
ivars.each { |name, value| instance_variable_set(name, value) }
|
|
622
|
+
if @attrs
|
|
623
|
+
@attrs = @attrs.transform_values { |v| CArray.__send__(:attr_coerce_value, v) }
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# --- attributes of the frame ------------------------------------------
|
|
628
|
+
#
|
|
629
|
+
# Key/value metadata about the table as a whole (a title, a source), apart
|
|
630
|
+
# from each column's own attributes. Keys and values follow the rules of
|
|
631
|
+
# CArray#set_attr: a Symbol or String key, stored as a String, and a
|
|
632
|
+
# JSON-compatible value, stored as a frozen copy.
|
|
633
|
+
#
|
|
634
|
+
# A frame made from this one takes its attributes when it holds the same
|
|
635
|
+
# table: a selection of rows (df[rows], filter, head, tail, sort_by,
|
|
636
|
+
# join(how: :semi / :anti)), a change of columns (select, append, drop,
|
|
637
|
+
# rename, ...), copy and dup. A frame computed from it -- a summary, a
|
|
638
|
+
# join, a reshape -- does not; ask the frame it came from. Each frame has
|
|
639
|
+
# its own Hash: setting an attribute on a frame made from this one leaves
|
|
640
|
+
# this one unchanged.
|
|
641
|
+
|
|
642
|
+
# The value of the attribute +key+, or nil when the frame has none.
|
|
643
|
+
def attr(key)
|
|
644
|
+
@attrs ? @attrs[CArray.__send__(:attr_normalize_key, key)] : nil
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# Set the attribute +key+ to +value+ and return the stored value.
|
|
648
|
+
def set_attr(key, value)
|
|
649
|
+
refuse_if_frozen
|
|
650
|
+
CArray.__send__(:attr_validate_value, value)
|
|
651
|
+
(@attrs ||= {})[CArray.__send__(:attr_normalize_key, key)] =
|
|
652
|
+
CArray.__send__(:attr_coerce_value, value)
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
# Set every key of +hash+, as set_attr sets one, and return self. Every
|
|
656
|
+
# value is checked before any is written.
|
|
657
|
+
def set_attrs(hash)
|
|
658
|
+
refuse_if_frozen
|
|
659
|
+
raise TypeError, "set_attrs takes a Hash (got #{hash.class})" unless hash.is_a?(Hash)
|
|
660
|
+
entries = hash.map do |key, value|
|
|
661
|
+
CArray.__send__(:attr_validate_value, value)
|
|
662
|
+
[CArray.__send__(:attr_normalize_key, key), CArray.__send__(:attr_coerce_value, value)]
|
|
663
|
+
end
|
|
664
|
+
unless entries.empty?
|
|
665
|
+
h = (@attrs ||= {})
|
|
666
|
+
entries.each { |k, v| h[k] = v }
|
|
667
|
+
end
|
|
668
|
+
self
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
# Remove the attribute +key+ and return its value, or nil.
|
|
672
|
+
def delete_attr(key)
|
|
673
|
+
k = CArray.__send__(:attr_normalize_key, key)
|
|
674
|
+
return nil unless @attrs&.key?(k)
|
|
675
|
+
refuse_if_frozen
|
|
676
|
+
@attrs.delete(k)
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
# A frozen Hash of the frame's attributes.
|
|
680
|
+
def attrs
|
|
681
|
+
(@attrs ? @attrs.dup : {}).freeze
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
# Whether the frame has any attribute, or the attribute +key+.
|
|
685
|
+
def has_attr?(key = nil)
|
|
686
|
+
return !(@attrs.nil? || @attrs.empty?) if key.nil?
|
|
687
|
+
!!@attrs&.key?(CArray.__send__(:attr_normalize_key, key))
|
|
531
688
|
end
|
|
532
689
|
|
|
533
690
|
# --- metadata readers (memo §13.2) ------------------------------------
|
|
@@ -559,18 +716,28 @@ class CAFrame
|
|
|
559
716
|
# Index column (CArray) or nil.
|
|
560
717
|
attr_reader :index
|
|
561
718
|
|
|
562
|
-
# name => data_type, freshly derived
|
|
719
|
+
# name => data_type, freshly derived; a Face column gives its class
|
|
720
|
+
# (CATime, CAString, ...), since a Face's data_type is the surface its
|
|
721
|
+
# kernels dispatch on, not what the column holds.
|
|
563
722
|
def data_types
|
|
564
723
|
h = {}
|
|
565
|
-
@columns.each { |k, v| h[k] = v.data_type }
|
|
724
|
+
@columns.each { |k, v| h[k] = v.face? ? v.class : v.data_type } # type-label: data_types returns the class itself, a value
|
|
566
725
|
h
|
|
567
726
|
end
|
|
568
727
|
|
|
728
|
+
# A frame of the same table with a different set of columns: it takes this
|
|
729
|
+
# frame's attributes.
|
|
569
730
|
private def rebuild(cols)
|
|
570
|
-
CAFrame.new(cols, axis_name: @axis_name, index: @index)
|
|
731
|
+
CAFrame.new(cols, axis_name: @axis_name, index: @index, attrs: @attrs)
|
|
571
732
|
end
|
|
572
733
|
|
|
734
|
+
# A column given to the frame: a CArray as it is; a Ruby Array typed by its
|
|
735
|
+
# values as from_records types a record's (build_record_column); anything
|
|
736
|
+
# else through its to_ca.
|
|
573
737
|
private def coerce_column(col)
|
|
738
|
+
if col.is_a?(Array)
|
|
739
|
+
return CAFrame.__send__(:build_record_column, col, col.size)
|
|
740
|
+
end
|
|
574
741
|
if col.is_a?(CAFrame)
|
|
575
742
|
raise ArgumentError,
|
|
576
743
|
"a CAFrame cannot be a column (it answers to_ca as a 2-D matrix); " \
|
|
@@ -680,8 +847,10 @@ class CAFrame
|
|
|
680
847
|
# column beforehand — silent
|
|
681
848
|
# promotion in a splice would
|
|
682
849
|
# hide schema drift.
|
|
850
|
+
splice_keep_attrs(new_cols[name], col)
|
|
683
851
|
end
|
|
684
852
|
new_index = splice_index(other, lo, hi)
|
|
853
|
+
splice_keep_attrs(new_index, @index) if new_index
|
|
685
854
|
@columns = new_cols
|
|
686
855
|
@index = new_index
|
|
687
856
|
# Read the new count off something the frame now holds, rather than
|
|
@@ -697,6 +866,13 @@ class CAFrame
|
|
|
697
866
|
self
|
|
698
867
|
end
|
|
699
868
|
|
|
869
|
+
# A splice edits this frame's table, so a spliced column keeps the
|
|
870
|
+
# attributes this frame's column had, whatever the rows put in carry.
|
|
871
|
+
private def splice_keep_attrs(result, own)
|
|
872
|
+
(result.attrs.keys - own.attrs.keys).each { |k| result.delete_attr(k) }
|
|
873
|
+
result.set_attrs(own.attrs)
|
|
874
|
+
end
|
|
875
|
+
|
|
700
876
|
# The index for a splice: head + other's index + tail. When this frame has
|
|
701
877
|
# an index, the spliced-in frame must have one too (its rows need labels).
|
|
702
878
|
private def splice_index(other, lo, hi)
|
|
@@ -774,7 +950,7 @@ class CAFrame
|
|
|
774
950
|
cols[name] = col[selector, *([nil] * (col.ndim - 1))]
|
|
775
951
|
end
|
|
776
952
|
new_index = @index ? @index[selector] : nil
|
|
777
|
-
CAFrame.new(cols, axis_name: @axis_name, index: new_index)
|
|
953
|
+
CAFrame.new(cols, axis_name: @axis_name, index: new_index, attrs: @attrs)
|
|
778
954
|
end
|
|
779
955
|
|
|
780
956
|
private def select_rows_keep_masked(selector)
|
|
@@ -802,8 +978,57 @@ class CAFrame
|
|
|
802
978
|
# aliased index would write through to the parent from a frame whose columns
|
|
803
979
|
# do not.
|
|
804
980
|
new_index = @index ? @index[keep].copy : nil
|
|
805
|
-
CAFrame.new(cols, axis_name: @axis_name, index: new_index)
|
|
981
|
+
CAFrame.new(cols, axis_name: @axis_name, index: new_index, attrs: @attrs)
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
# A column name given as an argument: a String, as the frame's column
|
|
985
|
+
# keys are. Symbols are the frame's options and types, never names.
|
|
986
|
+
private def column_key(name, verb)
|
|
987
|
+
CAFrame.__send__(:column_key, name, verb)
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
# A name a verb is given for a column -- or, with +what+, for the row
|
|
991
|
+
# axis -- is a String: a Symbol is a frame's option or type, never a
|
|
992
|
+
# name. Anything else is refused with TypeError "<verb>: <what> is a
|
|
993
|
+
# String (got :x)", +hint+ appended. A +verb+ that is an option, as
|
|
994
|
+
# "types:", already ends in its colon.
|
|
995
|
+
def self.column_key(name, verb, what = "a column name", hint: nil)
|
|
996
|
+
return name if name.is_a?(String)
|
|
997
|
+
message = "#{verb.end_with?(":") ? verb : "#{verb}:"} #{what} is a String (got #{name.inspect})"
|
|
998
|
+
message += "; #{hint}" if hint
|
|
999
|
+
raise TypeError, message
|
|
1000
|
+
end
|
|
1001
|
+
private_class_method :column_key
|
|
1002
|
+
|
|
1003
|
+
# A list of column names as a verb takes them: each a String, and none
|
|
1004
|
+
# twice.
|
|
1005
|
+
private def column_keys(names, verb)
|
|
1006
|
+
CAFrame.__send__(:refuse_repeated_names, names.map { |n| column_key(n, verb) }, verb)
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
# Column names a verb makes or takes, none of them twice. A frame holds
|
|
1010
|
+
# one column per name, so a repeated name among new columns would keep
|
|
1011
|
+
# only the last of them, and among the columns a verb takes would be
|
|
1012
|
+
# taken once or twice depending on the verb. Refused with ArgumentError
|
|
1013
|
+
# "<verb>: <what> <name> more than once", +hint+ appended. df[...] alone
|
|
1014
|
+
# takes a name twice, as Hash#values_at does.
|
|
1015
|
+
def self.refuse_repeated_names(names, verb, what = "names", hint: nil)
|
|
1016
|
+
dup = names.tally.find { |_, count| count > 1 }
|
|
1017
|
+
return names unless dup
|
|
1018
|
+
message = "#{verb}: #{what} #{dup.first.inspect} more than once"
|
|
1019
|
+
message += "; #{hint}" if hint
|
|
1020
|
+
raise ArgumentError, message
|
|
1021
|
+
end
|
|
1022
|
+
private_class_method :refuse_repeated_names
|
|
1023
|
+
|
|
1024
|
+
# The labels an index is made of from a key -- a group's first values,
|
|
1025
|
+
# the resample bins, the sorted pivot keys -- as the key column shows
|
|
1026
|
+
# them: +labels+, already of the key's data type, Face and unit, given
|
|
1027
|
+
# the key's attributes. Returns +labels+.
|
|
1028
|
+
def self.key_labels(key, labels)
|
|
1029
|
+
key.__send__(:carry_attrs, labels)
|
|
806
1030
|
end
|
|
1031
|
+
private_class_method :key_labels
|
|
807
1032
|
|
|
808
1033
|
private def positional_range?(range)
|
|
809
1034
|
(range.begin.nil? || range.begin.is_a?(Integer)) &&
|