carray 2.0.0 → 3.0.0
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/.yardopts +5 -25
- data/CHANGELOG.md +16 -0
- data/LICENSE +1 -1
- data/NEWS.md +3 -0
- data/README.md +128 -44
- data/carray.gemspec +22 -24
- data/ext/ca_array_pool.c +91 -0
- data/ext/ca_axis_descriptor.h +186 -0
- data/ext/ca_axis_dispatch.c +924 -0
- data/ext/ca_axis_group.c +1208 -0
- data/ext/ca_bincmp_dispatch.c +76 -0
- data/ext/ca_bincmp_dispatch.h +85 -0
- data/ext/ca_binop_dispatch.c +125 -0
- data/ext/ca_binop_dispatch.h +159 -0
- data/ext/ca_categorical_iterator.c +1375 -0
- data/ext/ca_compare.c +94 -0
- data/ext/ca_compare.h +26 -0
- data/ext/ca_composite_dispatch.c +414 -0
- data/ext/ca_composite_dispatch.h +116 -0
- data/ext/ca_for_buffer.h +96 -0
- data/ext/ca_for_each_element.h +241 -0
- data/ext/ca_group_iter.c +304 -0
- data/ext/ca_iter_substrate.h +325 -0
- data/ext/ca_kernel_iterator.c +4321 -0
- data/ext/ca_kernel_iterator.h +2603 -0
- data/ext/ca_moncmp_dispatch.c +37 -0
- data/ext/ca_moncmp_dispatch.h +62 -0
- data/ext/ca_monop_dispatch.c +200 -0
- data/ext/ca_monop_dispatch.h +235 -0
- data/ext/ca_obj_array.c +355 -359
- data/ext/ca_obj_bincmp.c +809 -0
- data/ext/ca_obj_binop.c +892 -0
- data/ext/ca_obj_bitarray.c +369 -164
- data/ext/ca_obj_bitfield.c +294 -234
- data/ext/ca_obj_block.c +189 -711
- data/ext/ca_obj_byte_swap.c +766 -0
- data/ext/ca_obj_const_string.c +965 -0
- data/ext/ca_obj_face.c +670 -0
- data/ext/ca_obj_face.h +247 -0
- data/ext/ca_obj_fake.c +228 -100
- data/ext/ca_obj_farray.c +54 -441
- data/ext/ca_obj_field.c +82 -529
- data/ext/ca_obj_fixlen_string.c +306 -0
- data/ext/ca_obj_grid.c +858 -440
- data/ext/ca_obj_meld.c +1034 -0
- data/ext/ca_obj_moncmp.c +569 -0
- data/ext/ca_obj_monop.c +1111 -0
- data/ext/ca_obj_object.c +774 -298
- data/ext/ca_obj_record.c +468 -0
- data/ext/ca_obj_reduce.c +97 -82
- data/ext/ca_obj_refer.c +569 -459
- data/ext/ca_obj_remap.c +475 -0
- data/ext/ca_obj_repeat.c +92 -477
- data/ext/ca_obj_roll.c +616 -0
- data/ext/ca_obj_select.c +344 -296
- data/ext/ca_obj_select_axis.c +1296 -0
- data/ext/ca_obj_shift.c +230 -792
- data/ext/ca_obj_source.c +78 -0
- data/ext/ca_obj_stack.c +1173 -0
- data/ext/ca_obj_stride.c +2501 -0
- data/ext/ca_obj_string.c +268 -0
- data/ext/ca_obj_tile.c +614 -0
- data/ext/ca_obj_time.c +546 -0
- data/ext/ca_obj_timedelta.c +435 -0
- data/ext/ca_obj_transpose.c +62 -516
- data/ext/ca_obj_triop.c +746 -0
- data/ext/ca_obj_unbound_repeat.c +208 -241
- data/ext/ca_obj_window.c +1131 -563
- data/ext/ca_op_byte_swap.c +175 -0
- data/ext/ca_op_ipower.c +319 -0
- data/ext/ca_op_powi.h +88 -0
- data/ext/ca_sort_kernels.h +132 -0
- data/ext/ca_sweep_engine.c +430 -0
- data/ext/ca_sweep_engine.h +157 -0
- data/ext/ca_transform_common.c +228 -0
- data/ext/ca_triop_dispatch.c +55 -0
- data/ext/ca_triop_dispatch.h +62 -0
- data/ext/carray.h +795 -402
- data/ext/carray_access.c +831 -711
- data/ext/carray_attribute.c +98 -330
- data/ext/carray_bincount.c +255 -0
- data/ext/carray_broadcast.c +283 -0
- data/ext/carray_call_cfunc.c +1360 -828
- data/ext/carray_call_cfunc.h +160 -0
- data/ext/carray_cast.c +1212 -301
- data/ext/carray_cast_func.rb +81 -40
- data/ext/carray_class.c +53 -63
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +350 -346
- data/ext/carray_copy.c +156 -268
- data/ext/carray_core.c +1342 -199
- data/ext/carray_count.c +312 -0
- data/ext/carray_data_type.c +43 -19
- data/ext/carray_element.c +585 -213
- data/ext/carray_factorize.c +2542 -0
- data/ext/carray_generate.c +230 -559
- data/ext/carray_histogram.c +490 -0
- data/ext/carray_hold.c +228 -0
- data/ext/carray_index_classifier.c +1035 -0
- data/ext/carray_index_classifier.h +27 -0
- data/ext/carray_internal.h +120 -0
- data/ext/carray_kernels_bincmp.c +4445 -0
- data/ext/carray_kernels_binop.c +10979 -0
- data/ext/carray_kernels_init.c +36 -0
- data/ext/carray_kernels_map.c +3466 -0
- data/ext/carray_kernels_moncmp.c +2096 -0
- data/ext/carray_kernels_monop.c +18312 -0
- data/ext/carray_kernels_reduce_aggregate.c +25836 -0
- data/ext/carray_kernels_reduce_boolean.c +329 -0
- data/ext/carray_kernels_reduce_cumulative.c +14592 -0
- data/ext/carray_kernels_reduce_extreme.c +16947 -0
- data/ext/carray_kernels_reduce_variance.c +3909 -0
- data/ext/carray_kernels_scan.c +3692 -0
- data/ext/carray_kernels_search.c +32137 -0
- data/ext/carray_kernels_sort.c +10625 -0
- data/ext/carray_kernels_triop.c +1391 -0
- data/ext/carray_lazy.c +567 -0
- data/ext/carray_loop.c +88 -200
- data/ext/carray_mask.c +848 -154
- data/ext/carray_math_kernel.h +120 -0
- data/ext/carray_mathfunc.c +10 -241
- data/ext/carray_median_percentile.c +1257 -0
- data/ext/carray_memory_view.c +1625 -0
- data/ext/carray_operator.c +1526 -318
- data/ext/carray_order.c +664 -1394
- data/ext/carray_partition.c +416 -0
- data/ext/carray_random.c +518 -0
- data/ext/carray_scatter.c +357 -0
- data/ext/carray_slab.c +1219 -0
- data/ext/carray_slab.h +84 -0
- data/ext/carray_sort.c +829 -0
- data/ext/carray_sort_kernel.c +620 -0
- data/ext/carray_struct.c +695 -0
- data/ext/carray_test.c +343 -229
- data/ext/carray_undef.c +34 -17
- data/ext/carray_utils.c +175 -74
- data/ext/extconf.rb +216 -55
- data/ext/mk_call_cfunc.rb +480 -0
- data/ext/mkkernel.rb +8842 -0
- data/ext/ruby_carray.c +202 -101
- data/ext/version.h +4 -14
- data/ext/version.rb +5 -13
- data/lib/carray/arrow_tensor.rb +401 -0
- data/lib/carray/attribute.rb +166 -0
- data/lib/carray/autoload_carray.rb +220 -0
- data/lib/carray/autoload_method_extension.rb +44 -0
- data/lib/carray/axis_group.rb +711 -0
- data/lib/carray/basics.rb +481 -0
- data/lib/carray/bincount_nd.rb +358 -0
- data/lib/carray/block_iterator.rb +604 -0
- data/lib/carray/boolean_reduce.rb +109 -0
- data/lib/carray/categorical.rb +561 -0
- data/lib/carray/categorical_iterator.rb +1062 -0
- data/lib/carray/complex.rb +150 -0
- data/lib/carray/conditional.rb +216 -0
- data/lib/carray/const_string.rb +228 -0
- data/lib/carray/construct.rb +139 -328
- data/lib/carray/core_extensions.rb +240 -0
- data/lib/carray/data_type_extension.rb +233 -0
- data/lib/carray/fixlen_string.rb +95 -0
- data/lib/carray/frame/concat.rb +132 -0
- data/lib/carray/frame/convert.rb +95 -0
- data/lib/carray/frame/csv_parser.rb +211 -0
- data/lib/carray/frame/frame.rb +649 -0
- data/lib/carray/frame/group.rb +186 -0
- data/lib/carray/frame/io.rb +164 -0
- data/lib/carray/frame/join.rb +248 -0
- data/lib/carray/frame/records.rb +99 -0
- data/lib/carray/frame/sort.rb +113 -0
- data/lib/carray/frame/verbs.rb +299 -0
- data/lib/carray/frame.rb +16 -0
- data/lib/carray/histogram.rb +512 -0
- data/lib/carray/inspect.rb +37 -20
- data/lib/carray/iterator.rb +57 -349
- data/lib/carray/lazy.rb +889 -0
- data/lib/carray/mask_gap_fill.rb +200 -0
- data/lib/carray/math.rb +78 -342
- data/lib/carray/meld_reduce.rb +289 -0
- data/lib/carray/methods/align_addr.rb +116 -0
- data/lib/carray/methods/bin.rb +128 -0
- data/lib/carray/methods/bincount.rb +87 -0
- data/lib/carray/methods/bit_string.rb +92 -0
- data/lib/carray/methods/broadcast.rb +63 -0
- data/lib/carray/methods/choose.rb +39 -0
- data/lib/carray/methods/composition.rb +280 -0
- data/lib/carray/methods/gather_nd.rb +206 -0
- data/lib/carray/methods/index.rb +39 -0
- data/lib/carray/methods/insert_block.rb +99 -0
- data/lib/carray/methods/is_in.rb +141 -0
- data/lib/carray/methods/join.rb +90 -0
- data/lib/carray/methods/locate_addr.rb +47 -0
- data/lib/carray/methods/mask_duplicates.rb +41 -0
- data/lib/carray/methods/meshgrid.rb +91 -0
- data/lib/carray/methods/mode.rb +126 -0
- data/lib/carray/methods/nunique.rb +46 -0
- data/lib/carray/methods/resize.rb +56 -0
- data/lib/carray/methods/snap.rb +156 -0
- data/lib/carray/methods/string_format.rb +57 -0
- data/lib/carray/methods/unique.rb +47 -0
- data/lib/carray/methods/value_counts.rb +71 -0
- data/lib/carray/mkmf.rb +124 -101
- data/lib/carray/runtime.rb +108 -0
- data/lib/carray/serialize.rb +478 -167
- data/lib/carray/slab_iterator.rb +292 -0
- data/lib/carray/stack.rb +291 -0
- data/lib/carray/string.rb +56 -180
- data/lib/carray/string_operation_extension.rb +289 -0
- data/lib/carray/struct.rb +335 -323
- data/lib/carray/struct_builder.rb +697 -0
- data/lib/carray/table.rb +41 -2
- data/lib/carray/time.rb +2255 -38
- data/lib/carray/window_iterator.rb +655 -0
- data/lib/carray.rb +55 -57
- metadata +163 -130
- data/Rakefile +0 -51
- data/TODO.md +0 -18
- data/ext/ca_iter_block.c +0 -257
- data/ext/ca_iter_dimension.c +0 -299
- data/ext/ca_iter_window.c +0 -214
- data/ext/ca_obj_mapping.c +0 -644
- data/ext/carray_iterator.c +0 -641
- data/ext/carray_math.rb +0 -850
- data/ext/carray_numeric.c +0 -259
- data/ext/carray_sort_addr.c +0 -254
- data/ext/carray_stat.c +0 -2100
- data/ext/carray_stat_proc.rb +0 -1999
- data/ext/mkmath.rb +0 -741
- data/ext/ruby_ccomplex.c +0 -509
- data/ext/ruby_float_func.c +0 -86
- data/lib/carray/array.rb +0 -8
- data/lib/carray/autoload/autoload_base.rb +0 -19
- data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
- data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
- data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
- data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
- data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
- data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
- data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
- data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
- data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
- data/lib/carray/autoload/autoload_gem_random.rb +0 -8
- data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
- data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
- data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
- data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
- data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
- data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
- data/lib/carray/autoload/autoload_object_link.rb +0 -1
- data/lib/carray/autoload/autoload_object_pack.rb +0 -2
- data/lib/carray/autoload.rb +0 -141
- data/lib/carray/basic.rb +0 -191
- data/lib/carray/broadcast.rb +0 -101
- data/lib/carray/compose.rb +0 -315
- data/lib/carray/convert.rb +0 -115
- data/lib/carray/info.rb +0 -110
- data/lib/carray/io/imagemagick.rb +0 -235
- data/lib/carray/mask.rb +0 -102
- data/lib/carray/math/histogram.rb +0 -177
- data/lib/carray/math/recurrence.rb +0 -93
- data/lib/carray/object/ca_obj_iterator.rb +0 -50
- data/lib/carray/object/ca_obj_link.rb +0 -50
- data/lib/carray/object/ca_obj_pack.rb +0 -99
- data/lib/carray/obsolete.rb +0 -256
- data/lib/carray/ordering.rb +0 -181
- data/lib/carray/testing.rb +0 -51
- data/lib/carray/transform.rb +0 -109
- data/misc/Methods.ja.md +0 -182
- data/misc/NOTE +0 -51
- data/spec/Classes/CABitfield_spec.rb +0 -58
- data/spec/Classes/CABlockIterator_spec.rb +0 -114
- data/spec/Classes/CABlock_spec.rb +0 -205
- data/spec/Classes/CAField_spec.rb +0 -39
- data/spec/Classes/CAGrid_spec.rb +0 -75
- data/spec/Classes/CAMap_spec.rb +0 -0
- data/spec/Classes/CAMapping_spec.rb +0 -105
- data/spec/Classes/CAObject_attribute_spec.rb +0 -33
- data/spec/Classes/CAObject_spec.rb +0 -33
- data/spec/Classes/CARefer_spec.rb +0 -93
- data/spec/Classes/CARepeat_spec.rb +0 -65
- data/spec/Classes/CASelect_spec.rb +0 -22
- data/spec/Classes/CAShift_spec.rb +0 -16
- data/spec/Classes/CAStruct_spec.rb +0 -71
- data/spec/Classes/CATranspose_spec.rb +0 -60
- data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
- data/spec/Classes/CAWindow_spec.rb +0 -54
- data/spec/Classes/CAWrap_spec.rb +0 -8
- data/spec/Classes/CArray_spec.rb +0 -184
- data/spec/Classes/CScalar_spec.rb +0 -55
- data/spec/Classes/ex1.rb +0 -46
- data/spec/Features/feature_130_spec.rb +0 -19
- data/spec/Features/feature_attributes_spec.rb +0 -280
- data/spec/Features/feature_boolean_spec.rb +0 -98
- data/spec/Features/feature_broadcast.rb +0 -116
- data/spec/Features/feature_cast_function.rb +0 -19
- data/spec/Features/feature_cast_spec.rb +0 -33
- data/spec/Features/feature_class_spec.rb +0 -84
- data/spec/Features/feature_complex_spec.rb +0 -42
- data/spec/Features/feature_composite_spec.rb +0 -124
- data/spec/Features/feature_convert_spec.rb +0 -46
- data/spec/Features/feature_copy_spec.rb +0 -123
- data/spec/Features/feature_creation_spec.rb +0 -84
- data/spec/Features/feature_element_spec.rb +0 -144
- data/spec/Features/feature_extream_spec.rb +0 -54
- data/spec/Features/feature_generate_spec.rb +0 -74
- data/spec/Features/feature_index_spec.rb +0 -69
- data/spec/Features/feature_mask_spec.rb +0 -580
- data/spec/Features/feature_math_spec.rb +0 -97
- data/spec/Features/feature_order_spec.rb +0 -146
- data/spec/Features/feature_ref_store_spec.rb +0 -209
- data/spec/Features/feature_serialization_spec.rb +0 -125
- data/spec/Features/feature_stat_spec.rb +0 -397
- data/spec/Features/feature_virtual_spec.rb +0 -48
- data/spec/Features/method_eq_spec.rb +0 -81
- data/spec/Features/method_is_nan_spec.rb +0 -12
- data/spec/Features/method_map_spec.rb +0 -54
- data/spec/Features/method_max_with.rb +0 -20
- data/spec/Features/method_min_with.rb +0 -19
- data/spec/Features/method_ne_spec.rb +0 -18
- data/spec/Features/method_project_spec.rb +0 -188
- data/spec/Features/method_ref_spec.rb +0 -27
- data/spec/Features/method_round_spec.rb +0 -11
- data/spec/Features/method_s_linspace_spec.rb +0 -48
- data/spec/Features/method_s_span_spec.rb +0 -14
- data/spec/Features/method_seq_spec.rb +0 -47
- data/spec/Features/method_sort_with.rb +0 -43
- data/spec/Features/method_sorted_with.rb +0 -29
- data/spec/Features/method_span_spec.rb +0 -42
- data/spec/Features/method_wrap_readonly_spec.rb +0 -43
- data/spec/UnitTest/test_CAVirtual.rb +0 -214
- data/spec/spec_all.rb +0 -10
- data/utils/ca_ase.rb +0 -21
- data/utils/ca_methods.rb +0 -15
- data/utils/cast_checker.rb +0 -30
- data/utils/convert_test.rb +0 -73
- data/utils/extract_yard.rb +0 -22
- data/utils/guess_shape.rb +0 -76
- data/utils/monkey_patch_methods.rb +0 -62
- data/utils/remove_resource_fork.sh +0 -5
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# CAFrame row sort (memo §5, spine = addressing + gather).
|
|
2
|
+
#
|
|
3
|
+
# Two entry points, both delegating to the multi-key CArray.sort_addr -- the
|
|
4
|
+
# class-method lexicographic sort that returns flat row addresses (distinct from
|
|
5
|
+
# the single-key instance CArray#sort_index) -- and gathering every column and
|
|
6
|
+
# the index by that address into a view-frame in key order:
|
|
7
|
+
#
|
|
8
|
+
# sort_by_key(*keys, order:, masked_position:) -- sort by named columns, with
|
|
9
|
+
# per-key or blanket ascending/descending.
|
|
10
|
+
# sort_by { |f| ... } -- sort by key CArrays the block builds (the escape,
|
|
11
|
+
# §4.3): derived or composite keys are ordinary column
|
|
12
|
+
# math.
|
|
13
|
+
#
|
|
14
|
+
# sort_addr is ascending, so a descending key is expressed by replacing it with
|
|
15
|
+
# its dense descending rank (CArray#order(descending: true, method: :dense)):
|
|
16
|
+
# this works for every dtype (unlike negation, which cannot reverse a string and
|
|
17
|
+
# silently wraps an unsigned integer), and the *dense* rank keeps equal values
|
|
18
|
+
# on one rank so ties fall through to later keys in a multi-key sort.
|
|
19
|
+
# masked_position (CArray.sort_addr's kwarg) places masked key rows first or
|
|
20
|
+
# last (default last).
|
|
21
|
+
|
|
22
|
+
class CAFrame
|
|
23
|
+
# Sort rows by one or more key columns, lexicographic (the first key is
|
|
24
|
+
# primary), returning a view-frame. Delegates to +CArray.sort_addr+ for the
|
|
25
|
+
# row permutation and gathers every column and the index by it.
|
|
26
|
+
#
|
|
27
|
+
# Each key is a **column name** (or the index axis name), or a
|
|
28
|
+
# **`[name, :asc | :desc]`** pair for a per-key direction. The +order:+ keyword
|
|
29
|
+
# is the direction for bare-name keys (default +:asc+). +masked_position:+
|
|
30
|
+
# sends masked key rows to the +:last+ (default) or +:first+ end.
|
|
31
|
+
#
|
|
32
|
+
# df.sort_by_key("temp") # one column, ascending
|
|
33
|
+
# df.sort_by_key("station", "temp") # lexicographic
|
|
34
|
+
# df.sort_by_key("temp", order: :desc) # all keys descending
|
|
35
|
+
# df.sort_by_key(["station", :asc], ["temp", :desc]) # per-key direction
|
|
36
|
+
# df.sort_by_key("temp", masked_position: :first) # masked rows first
|
|
37
|
+
# df.sort_by_key("time") # by the index
|
|
38
|
+
def sort_by_key(*specs, order: :asc, masked_position: :last)
|
|
39
|
+
raise ArgumentError, "sort_by_key requires at least one key" if specs.empty?
|
|
40
|
+
validate_sort_order(order)
|
|
41
|
+
keys = specs.map do |spec|
|
|
42
|
+
name, dir = parse_sort_key_spec(spec, order)
|
|
43
|
+
col = sort_key_column(name)
|
|
44
|
+
dir == :desc ? col.order(descending: true, method: :dense) : col
|
|
45
|
+
end
|
|
46
|
+
select_rows(CArray.sort_addr(*keys, masked_position: masked_position))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Sort rows by key columns the block builds (memo §4.3 escape). The block
|
|
50
|
+
# receives the frame and returns a key CArray, or an Array of them for a
|
|
51
|
+
# multi-key lexicographic sort; rows are sorted ascending by +CArray.sort_addr+
|
|
52
|
+
# over those keys and gathered into a view-frame. This is the sort sibling of
|
|
53
|
+
# +filter+: where +sort_by_key+ takes plain column names, the block form lets
|
|
54
|
+
# you build any key with CArray ops -- a descending key is its dense
|
|
55
|
+
# descending rank, derived or composite keys are ordinary column math.
|
|
56
|
+
# +masked_position:+ places masked key rows first or last (default last).
|
|
57
|
+
#
|
|
58
|
+
# df.sort_by { |f| (f["temp"] - target).abs } # nearest-to-target first
|
|
59
|
+
# df.sort_by { |f| f["temp"].order(descending: true, method: :dense) }
|
|
60
|
+
def sort_by(masked_position: :last)
|
|
61
|
+
raise ArgumentError, "sort_by requires a block" unless block_given?
|
|
62
|
+
keys = yield(self)
|
|
63
|
+
keys = [keys] unless keys.is_a?(Array)
|
|
64
|
+
raise ArgumentError, "sort_by block must return a key CArray or an Array of them" if keys.empty?
|
|
65
|
+
keys.each_with_index do |k, i|
|
|
66
|
+
unless k.is_a?(CArray)
|
|
67
|
+
raise ArgumentError, "sort_by key #{i} must be a CArray (got #{k.class})"
|
|
68
|
+
end
|
|
69
|
+
unless k.ndim == 1 && k.shape[0] == nrow
|
|
70
|
+
raise ArgumentError,
|
|
71
|
+
"sort_by key #{i} must be a 1-D column of length #{nrow} (got shape #{k.shape.inspect})"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
select_rows(CArray.sort_addr(*keys, masked_position: masked_position))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private def parse_sort_key_spec(spec, default_order)
|
|
78
|
+
case spec
|
|
79
|
+
when String, Symbol
|
|
80
|
+
[spec, default_order]
|
|
81
|
+
when Array
|
|
82
|
+
unless spec.size == 2 && (spec[0].is_a?(String) || spec[0].is_a?(Symbol))
|
|
83
|
+
raise ArgumentError, "sort key must be a name or [name, :asc|:desc], got #{spec.inspect}"
|
|
84
|
+
end
|
|
85
|
+
validate_sort_order(spec[1])
|
|
86
|
+
spec
|
|
87
|
+
else
|
|
88
|
+
raise ArgumentError, "sort key must be a name or [name, :asc|:desc], got #{spec.class}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private def validate_sort_order(dir)
|
|
93
|
+
return if dir == :asc || dir == :desc
|
|
94
|
+
raise ArgumentError, "sort order must be :asc or :desc, got #{dir.inspect}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private def sort_key_column(name)
|
|
98
|
+
key = name.to_s
|
|
99
|
+
col = if @columns.key?(key)
|
|
100
|
+
@columns[key]
|
|
101
|
+
elsif @index && @axis_name == key
|
|
102
|
+
@index
|
|
103
|
+
else
|
|
104
|
+
raise KeyError, "no column #{key.inspect}"
|
|
105
|
+
end
|
|
106
|
+
unless col.ndim == 1
|
|
107
|
+
raise ArgumentError,
|
|
108
|
+
"sort_by_key: key column #{key.inspect} is #{col.ndim}-D; " \
|
|
109
|
+
"sort by a scalar (1-D) column"
|
|
110
|
+
end
|
|
111
|
+
col
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# CAFrame column verbs (memo §11.4, §12-A, §13.2).
|
|
2
|
+
#
|
|
3
|
+
# Each verb touches this frame's own columns Hash (per-frame, §12-B) and
|
|
4
|
+
# returns self so calls chain. Column data is still shared with any parent
|
|
5
|
+
# (view-frame), but membership — which columns this frame names — is local:
|
|
6
|
+
# append/drop/rename here never change a parent's column set.
|
|
7
|
+
|
|
8
|
+
class CAFrame
|
|
9
|
+
# Add (or replace) a column, returning a new frame — the column set changes,
|
|
10
|
+
# so the result is a different table (memo §3.8). The column is shared by
|
|
11
|
+
# reference; the envelope is cheap. Chain or reassign: +df = df.append(...)+.
|
|
12
|
+
def append(name, col)
|
|
13
|
+
key = name.to_s
|
|
14
|
+
ca = coerce_column(col)
|
|
15
|
+
rebuild(@columns.merge(key => ca))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Remove one or more columns, returning a new frame (column set changes,
|
|
19
|
+
# memo §3.8). Column data is untouched and shared with the original.
|
|
20
|
+
def drop(*names)
|
|
21
|
+
cols = @columns.dup
|
|
22
|
+
names.each do |name|
|
|
23
|
+
key = name.to_s
|
|
24
|
+
raise KeyError, "no column #{key.inspect}" unless cols.key?(key)
|
|
25
|
+
cols.delete(key)
|
|
26
|
+
end
|
|
27
|
+
rebuild(cols)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Rename columns, returning a new frame (column names change, memo §3.8).
|
|
31
|
+
# Column order is preserved (memo §13.2 "column order = insertion order");
|
|
32
|
+
# columns are shared by reference.
|
|
33
|
+
def rename(mapping)
|
|
34
|
+
norm = {}
|
|
35
|
+
mapping.each do |old, new|
|
|
36
|
+
o = old.to_s
|
|
37
|
+
n = new.to_s
|
|
38
|
+
raise KeyError, "no column #{o.inspect}" unless @columns.key?(o)
|
|
39
|
+
if n != o && @columns.key?(n)
|
|
40
|
+
raise ArgumentError, "rename target #{n.inspect} already exists"
|
|
41
|
+
end
|
|
42
|
+
norm[o] = n
|
|
43
|
+
end
|
|
44
|
+
rebuilt = {}
|
|
45
|
+
@columns.each { |k, v| rebuilt[norm[k] || k] = v }
|
|
46
|
+
rebuild(rebuilt)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Mask cells of a column that equal +value+ (sentinel -> mask, memo §11.4).
|
|
50
|
+
# In-place on the column (write-through, §4.3): numeric/object columns
|
|
51
|
+
# mask in place; a categorical column's codes are read-only (§13.4) so this
|
|
52
|
+
# raises — recode by rebuilding and rebinding instead.
|
|
53
|
+
def mask_eq(name, value)
|
|
54
|
+
self[name][:eq, value] = UNDEF
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Cast columns to a data type and rebind them (memo §11.4). Uses to_type,
|
|
59
|
+
# so parse failures on string columns become UNDEF (parse-mask, §6-2).
|
|
60
|
+
# Three call shapes, disambiguated by the fact that column names are always
|
|
61
|
+
# Strings and types always Symbols (§3.7):
|
|
62
|
+
#
|
|
63
|
+
# cast("temp", :float64) # one column (chains)
|
|
64
|
+
# cast("temp" => :float64, "rh" => :int32) # name => type map
|
|
65
|
+
# cast(["temp", "rh"] => :float64) # names sharing one type
|
|
66
|
+
#
|
|
67
|
+
# A map key may be a single name or an Array of names; the value is the
|
|
68
|
+
# target type. Returns self so calls chain.
|
|
69
|
+
def cast(name_or_map, type = nil)
|
|
70
|
+
if name_or_map.is_a?(Hash)
|
|
71
|
+
unless type.nil?
|
|
72
|
+
raise ArgumentError, "cast(map) takes no positional type argument"
|
|
73
|
+
end
|
|
74
|
+
name_or_map.each do |names, t|
|
|
75
|
+
Array(names).each { |name| cast_one(name, t) }
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
cast_one(name_or_map, type)
|
|
79
|
+
end
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Bring every column to one common data type and rebind them (memo §11.4).
|
|
84
|
+
# The frame-level counterpart of +CArray.promote_list+: where +cast+ forces
|
|
85
|
+
# named columns to a type, +promote+ widens the whole table until it has a
|
|
86
|
+
# single type. Returns self so calls chain (§3.8 -- a type-interpretation
|
|
87
|
+
# change), and like +cast+ it allocates fresh columns, so a parent frame
|
|
88
|
+
# sharing the old ones is untouched. The index is not a column and is left
|
|
89
|
+
# alone.
|
|
90
|
+
#
|
|
91
|
+
# df.promote # the common type CArray.result_type would pick
|
|
92
|
+
# df.promote(:object) # force the widest type: anything fits
|
|
93
|
+
#
|
|
94
|
+
# Without an argument the common type is whatever +promote_list+ picks --
|
|
95
|
+
# the same decision +to_ca+ / +CArray.stack+ make internally, so
|
|
96
|
+
# +df.promote+ is exactly "make this frame stackable". Columns that are
|
|
97
|
+
# already uniform (including a uniformly Face-typed frame) are left as they
|
|
98
|
+
# are.
|
|
99
|
+
#
|
|
100
|
+
# With an argument the type must be a widening for every column: promoting
|
|
101
|
+
# is not the place to lose values, so a narrowing target raises and points
|
|
102
|
+
# at +cast+, which is the verb that forces. +:object+ is the widest type
|
|
103
|
+
# and therefore always accepted -- it is the way a frame mixing text,
|
|
104
|
+
# Face-typed and numeric columns becomes a single-type table (and so the
|
|
105
|
+
# way +to_ca+ can hand back a matrix for it).
|
|
106
|
+
def promote(type = nil)
|
|
107
|
+
return self if @columns.empty?
|
|
108
|
+
type.nil? ? promote_to_common : promote_to_type(type)
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Parse a string column into a time column and rebind it (memo §11.2).
|
|
113
|
+
# The column must be string-bearing (an object CArray of Strings, or a
|
|
114
|
+
# CAString / CAConstString / CAFixlenString) — this is the "text -> time"
|
|
115
|
+
# mode, distinct from the integer-serial mode of +to_time+. Delegates to
|
|
116
|
+
# +CArray.time(col, on_error: :mask)+: +format+ picks strptime parsing
|
|
117
|
+
# (auto-detect when nil), +unit+ the storage resolution; masked / nil and
|
|
118
|
+
# unparseable cells become UNDEF (bulk column parse tolerates bad cells).
|
|
119
|
+
# Make it the index with +set_index+ afterward.
|
|
120
|
+
#
|
|
121
|
+
# df.parse_to_time("time").set_index("time")
|
|
122
|
+
def parse_to_time(name, format = nil, unit: :s)
|
|
123
|
+
key = name.to_s
|
|
124
|
+
col = @columns.fetch(key) { raise KeyError, "no column #{key.inspect}" }
|
|
125
|
+
unless string_column?(col)
|
|
126
|
+
raise ArgumentError,
|
|
127
|
+
"parse_to_time needs a string column (object / CAString / " \
|
|
128
|
+
"CAConstString / CAFixlenString); #{key.inspect} is #{col.data_type}"
|
|
129
|
+
end
|
|
130
|
+
@columns[key] = CArray.time(col, format: format, unit: unit, on_error: :mask)
|
|
131
|
+
self
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Reinterpret an integer column as time serial counts and rebind it (memo
|
|
135
|
+
# §11.2). This is the "serial -> time" mode: each value is a count of
|
|
136
|
+
# +unit+ resolution since +epoch+ (default the Unix epoch, 1970-01-01 UTC).
|
|
137
|
+
# +epoch+ takes any time literal (String / Time / Integer), so a column
|
|
138
|
+
# measured from another origin — a netCDF "hours since 1990-01-01" axis, an
|
|
139
|
+
# Excel serial date (epoch "1899-12-30", unit :D) — converts directly.
|
|
140
|
+
#
|
|
141
|
+
# A float column is accepted only when every value is whole (no fractional
|
|
142
|
+
# part); a fractional serial has sub-unit precision that a finer +unit+ should
|
|
143
|
+
# carry, so it raises rather than silently truncate. Make it the index with
|
|
144
|
+
# +set_index+ afterward.
|
|
145
|
+
#
|
|
146
|
+
# df.to_time("time", unit: :h, epoch: "1990-01-01").set_index("time")
|
|
147
|
+
def to_time(name, unit: :s, epoch: nil)
|
|
148
|
+
key = name.to_s
|
|
149
|
+
col = @columns.fetch(key) { raise KeyError, "no column #{key.inspect}" }
|
|
150
|
+
raw = integer_serial_column(col, key)
|
|
151
|
+
if epoch
|
|
152
|
+
raw = raw + CArray.time(epoch, unit: unit).ticks[0]
|
|
153
|
+
end
|
|
154
|
+
@columns[key] = raw.time(unit: unit)
|
|
155
|
+
self
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Fill masked cells of a column (memo §6, §11.8). A Symbol selects a
|
|
159
|
+
# scan method; anything else is a constant fill value. The fill is
|
|
160
|
+
# write-through: it edits the live shared column rather than rebinding a
|
|
161
|
+
# filled copy (memo §3.8). Returns self so calls chain.
|
|
162
|
+
#
|
|
163
|
+
# fill("temp", :ffill) # forward hold (carry last valid value)
|
|
164
|
+
# fill("temp", :bfill) # backward hold (carry next valid value)
|
|
165
|
+
# fill("temp", :linear) # linear interpolation; x = this frame's index
|
|
166
|
+
# # coordinate when present, else the cell position
|
|
167
|
+
# fill("temp", 0.0) # constant fill
|
|
168
|
+
#
|
|
169
|
+
# :ffill / :bfill work for any writable column data_type (numeric, time,
|
|
170
|
+
# object, fixlen, …); :linear needs a numeric or time (CATime /
|
|
171
|
+
# CATimedelta) column. A categorical column's codes are read-only
|
|
172
|
+
# (memo §13.4), so any fill on it raises — rebind a filled copy instead:
|
|
173
|
+
# `df.append(name, df[name].strip_mask(method: :forward))`.
|
|
174
|
+
def fill(name, method_or_value)
|
|
175
|
+
key = name.to_s
|
|
176
|
+
raise KeyError, "no column #{key.inspect}" unless @columns.key?(key)
|
|
177
|
+
col = @columns[key]
|
|
178
|
+
case method_or_value
|
|
179
|
+
when :ffill then col.unmask(method: :forward)
|
|
180
|
+
when :bfill then col.unmask(method: :backward)
|
|
181
|
+
when :linear then fill_linear_column(key, col)
|
|
182
|
+
when Symbol
|
|
183
|
+
raise ArgumentError,
|
|
184
|
+
"fill: unknown method #{method_or_value.inspect} " \
|
|
185
|
+
"(:ffill | :bfill | :linear, or a constant fill value)"
|
|
186
|
+
else
|
|
187
|
+
col.unmask(method_or_value) # constant fill, in place
|
|
188
|
+
end
|
|
189
|
+
self
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
private def fill_linear_column(key, col)
|
|
193
|
+
# A time column interpolates through CATime#linear_fetch /
|
|
194
|
+
# CATimedelta#linear_fetch, which keep the Face and its unit.
|
|
195
|
+
time_face = col.is_a?(CATime) || col.is_a?(CATimedelta)
|
|
196
|
+
unless col.numeric? || time_face
|
|
197
|
+
raise ArgumentError,
|
|
198
|
+
"fill(#{key.inspect}, :linear): numeric or time column required " \
|
|
199
|
+
"(got #{col.data_type_name})"
|
|
200
|
+
end
|
|
201
|
+
# Without an index coordinate x is the cell position, which is exactly what
|
|
202
|
+
# the core scan already interpolates against -- time columns included.
|
|
203
|
+
return col.unmask(method: :linear) if @index.nil?
|
|
204
|
+
present = col.is_not_masked
|
|
205
|
+
return if present.count(true) < 2
|
|
206
|
+
addr = @index[present].linear_section(@index)
|
|
207
|
+
if time_face
|
|
208
|
+
filled = col[present].linear_fetch(addr) # UNDEF beyond the valid span
|
|
209
|
+
# Write through the storage: a bulk store into the Face itself would try
|
|
210
|
+
# to cast int64 ticks to its fixlen surface. Both sides carry the same
|
|
211
|
+
# unit (selection preserves it), so the ticks land exactly, mask included.
|
|
212
|
+
col.parent[] = filled.parent
|
|
213
|
+
return
|
|
214
|
+
end
|
|
215
|
+
yvalid = col.value.float64[present]
|
|
216
|
+
col[] = yvalid.linear_fetch(addr).to_type(col.data_type).mask_invalid # write-through
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private def cast_one(name, type)
|
|
220
|
+
key = name.to_s
|
|
221
|
+
raise KeyError, "no column #{key.inspect}" unless @columns.key?(key)
|
|
222
|
+
@columns[key] = @columns[key].to_type(type)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# +promote+ with no target: let +promote_list+ decide the common type --
|
|
226
|
+
# the same call +to_ca+ / +CArray.stack+ make -- then materialise each
|
|
227
|
+
# column it had to coerce. +promote_list+ hands a column back untouched
|
|
228
|
+
# when it needs no coercion, so identity says which ones to rebind.
|
|
229
|
+
private def promote_to_common
|
|
230
|
+
cols = @columns.values
|
|
231
|
+
begin
|
|
232
|
+
promoted = CArray.promote_list(cols)
|
|
233
|
+
rescue ArgumentError, RuntimeError => e
|
|
234
|
+
raise ArgumentError,
|
|
235
|
+
"#{e.message} -- promote(:object) brings every column to its " \
|
|
236
|
+
"surface values, which any column set can share; a numeric target " \
|
|
237
|
+
"works too when every Face column declares #to_numeric"
|
|
238
|
+
end
|
|
239
|
+
@columns.keys.each_with_index do |key, i|
|
|
240
|
+
coerced = promoted[i]
|
|
241
|
+
next if coerced.equal?(cols[i])
|
|
242
|
+
@columns[key] = cols[i].to_type(coerced.data_type)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# +promote+ with a target: every column must widen into it.
|
|
247
|
+
private def promote_to_type(type)
|
|
248
|
+
unless type.is_a?(Symbol)
|
|
249
|
+
raise ArgumentError,
|
|
250
|
+
"promote takes a data type Symbol (got #{type.class}); " \
|
|
251
|
+
"class-shaped targets are not promotion destinations"
|
|
252
|
+
end
|
|
253
|
+
@columns.each_key do |key|
|
|
254
|
+
col = @columns[key]
|
|
255
|
+
# A Face column answers for itself: :object is its surface values, a
|
|
256
|
+
# numeric target is whatever it declares in #to_numeric (and a TypeError
|
|
257
|
+
# naming that method when it declares nothing). result_type has nothing
|
|
258
|
+
# to say about a surface it cannot read, so the widening check -- which
|
|
259
|
+
# is about primitive promotion -- applies to plain columns only.
|
|
260
|
+
refuse_narrowing(key, col, type) unless col.face?
|
|
261
|
+
@columns[key] = col.to_type(type)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
private def refuse_narrowing(key, col, type)
|
|
266
|
+
common = begin
|
|
267
|
+
CArray.result_type(col, type)
|
|
268
|
+
rescue StandardError => e
|
|
269
|
+
raise ArgumentError,
|
|
270
|
+
"promote: column #{key.inspect} (#{col.data_type}) has no common " \
|
|
271
|
+
"type with #{type.inspect} (#{e.message})"
|
|
272
|
+
end
|
|
273
|
+
return if common == type
|
|
274
|
+
raise ArgumentError,
|
|
275
|
+
"promote widens: column #{key.inspect} (#{col.data_type}) would " \
|
|
276
|
+
"narrow to #{type.inspect} -- cast is the verb that forces a " \
|
|
277
|
+
"lossy change"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
private def string_column?(col)
|
|
281
|
+
col.is_a?(CArray::StringOperationMixin) || col.data_type == :object
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
private def integer_serial_column(col, key)
|
|
285
|
+
if INTEGER_TYPES.include?(col.data_type)
|
|
286
|
+
col.to_type(:int64)
|
|
287
|
+
elsif col.data_type == :float32 || col.data_type == :float64
|
|
288
|
+
unless col.floor.eq(col).all
|
|
289
|
+
raise ArgumentError,
|
|
290
|
+
"to_time: float column #{key.inspect} has fractional values; " \
|
|
291
|
+
"use a finer unit: or convert to integer counts explicitly"
|
|
292
|
+
end
|
|
293
|
+
col.to_type(:int64)
|
|
294
|
+
else
|
|
295
|
+
raise ArgumentError,
|
|
296
|
+
"to_time needs an integer serial column; #{key.inspect} is #{col.data_type}"
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
data/lib/carray/frame.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# CAFrame — a DataFrame whose columns are real CArrays.
|
|
2
|
+
#
|
|
3
|
+
# Thin loader. The frame is a small Ruby layer over CArray: columns are
|
|
4
|
+
# genuine CArrays, so every CArray operation (mask / view / Face /
|
|
5
|
+
# memory_view) is reachable by escaping to a column. See
|
|
6
|
+
# devel/MEMO_DATAFRAME_ON_CARRAY.md for the design.
|
|
7
|
+
|
|
8
|
+
require 'carray/frame/frame'
|
|
9
|
+
require 'carray/frame/verbs'
|
|
10
|
+
require 'carray/frame/convert'
|
|
11
|
+
require 'carray/frame/io'
|
|
12
|
+
require 'carray/frame/records'
|
|
13
|
+
require 'carray/frame/group'
|
|
14
|
+
require 'carray/frame/join'
|
|
15
|
+
require 'carray/frame/concat'
|
|
16
|
+
require 'carray/frame/sort'
|