carray 2.0.1 → 3.0.1
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 +6 -25
- data/CHANGELOG.md +338 -0
- data/{NEWS.md → CHANGELOG.v1.md} +3 -0
- data/LICENSE +1 -1
- data/README.md +120 -36
- data/carray.gemspec +32 -30
- 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 +1244 -0
- data/ext/ca_bincmp_dispatch.c +76 -0
- data/ext/ca_bincmp_dispatch.h +85 -0
- data/ext/ca_binop_dispatch.c +124 -0
- data/ext/ca_binop_dispatch.h +152 -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 +239 -0
- data/ext/ca_group_iter.c +304 -0
- data/ext/ca_iter_substrate.h +325 -0
- data/ext/ca_kernel_iterator.c +4367 -0
- data/ext/ca_kernel_iterator.h +2596 -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 +839 -0
- data/ext/ca_obj_binop.c +948 -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 +967 -0
- data/ext/ca_obj_face.c +750 -0
- data/ext/ca_obj_face.h +279 -0
- data/ext/ca_obj_fake.c +239 -100
- data/ext/ca_obj_farray.c +54 -441
- data/ext/ca_obj_field.c +82 -529
- data/ext/ca_obj_fixlen_string.c +308 -0
- data/ext/ca_obj_grid.c +866 -440
- data/ext/ca_obj_meld.c +1039 -0
- data/ext/ca_obj_moncmp.c +588 -0
- data/ext/ca_obj_monop.c +1123 -0
- data/ext/ca_obj_object.c +866 -296
- data/ext/ca_obj_record.c +470 -0
- data/ext/ca_obj_reduce.c +97 -82
- data/ext/ca_obj_refer.c +593 -459
- data/ext/ca_obj_remap.c +475 -0
- data/ext/ca_obj_repeat.c +92 -477
- data/ext/ca_obj_roll.c +624 -0
- data/ext/ca_obj_select.c +344 -296
- data/ext/ca_obj_select_axis.c +1306 -0
- data/ext/ca_obj_shift.c +231 -793
- data/ext/ca_obj_source.c +78 -0
- data/ext/ca_obj_stack.c +1173 -0
- data/ext/ca_obj_stride.c +2584 -0
- data/ext/ca_obj_string.c +270 -0
- data/ext/ca_obj_tile.c +622 -0
- data/ext/ca_obj_time.c +548 -0
- data/ext/ca_obj_timedelta.c +437 -0
- data/ext/ca_obj_transpose.c +62 -516
- data/ext/ca_obj_triop.c +785 -0
- data/ext/ca_obj_window.c +1202 -565
- data/ext/ca_op_byte_swap.c +175 -0
- data/ext/ca_op_cmplx64.h +123 -0
- data/ext/ca_op_ipower.c +316 -0
- data/ext/ca_op_powi.h +88 -0
- data/ext/ca_sort_kernels.h +132 -0
- data/ext/ca_sweep_engine.c +473 -0
- data/ext/ca_sweep_engine.h +166 -0
- data/ext/ca_transform_common.c +235 -0
- data/ext/ca_triop_dispatch.c +55 -0
- data/ext/ca_triop_dispatch.h +62 -0
- data/ext/carray.h +810 -420
- data/ext/carray_access.c +873 -731
- data/ext/carray_attribute.c +98 -329
- data/ext/carray_bincount.c +255 -0
- data/ext/carray_broadcast.c +376 -0
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +2897 -874
- data/ext/carray_call_cfunc.h +313 -0
- data/ext/carray_cast.c +1264 -315
- 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 +168 -270
- data/ext/carray_core.c +1396 -206
- 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 +1021 -0
- data/ext/carray_index_classifier.h +27 -0
- data/ext/carray_internal.h +136 -0
- data/ext/carray_kernels_bincmp.c +4446 -0
- data/ext/carray_kernels_binop.c +11001 -0
- data/ext/carray_kernels_init.c +1131 -0
- data/ext/carray_kernels_map.c +3467 -0
- data/ext/carray_kernels_moncmp.c +2097 -0
- data/ext/carray_kernels_monop.c +18313 -0
- data/ext/carray_kernels_reduce_aggregate.c +25837 -0
- data/ext/carray_kernels_reduce_boolean.c +330 -0
- data/ext/carray_kernels_reduce_cumulative.c +14593 -0
- data/ext/carray_kernels_reduce_extreme.c +16948 -0
- data/ext/carray_kernels_reduce_variance.c +3910 -0
- data/ext/carray_kernels_scan.c +3693 -0
- data/ext/carray_kernels_search.c +32138 -0
- data/ext/carray_kernels_sort.c +10626 -0
- data/ext/carray_kernels_triop.c +1392 -0
- data/ext/carray_lazy.c +737 -0
- data/ext/carray_loop.c +88 -200
- data/ext/carray_mask.c +853 -158
- 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 +1650 -0
- data/ext/carray_operator.c +1525 -320
- 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 +234 -55
- data/ext/mk_call_cfunc.rb +671 -0
- data/ext/mkkernel.rb +9096 -0
- data/ext/ruby_carray.c +211 -108
- 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 +239 -0
- data/lib/carray/autoload_method_extension.rb +45 -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 +614 -0
- data/lib/carray/boolean_reduce.rb +109 -0
- data/lib/carray/categorical.rb +561 -0
- data/lib/carray/categorical_iterator.rb +1084 -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 +160 -328
- data/lib/carray/core_extensions.rb +297 -0
- data/lib/carray/data_type_extension.rb +250 -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 +642 -0
- data/lib/carray/frame/group.rb +186 -0
- data/lib/carray/frame/io.rb +321 -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 +316 -0
- data/lib/carray/frame.rb +16 -0
- data/lib/carray/fuse_source.rb +123 -0
- data/lib/carray/fusion.rb +218 -0
- data/lib/carray/histogram.rb +512 -0
- data/lib/carray/inspect.rb +37 -26
- data/lib/carray/iterator.rb +58 -349
- data/lib/carray/lazy.rb +941 -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 +52 -0
- data/lib/carray/methods/mask_duplicates.rb +41 -0
- data/lib/carray/methods/meshgrid.rb +90 -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 +161 -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 +89 -0
- data/lib/carray/serialize.rb +478 -167
- data/lib/carray/slab_iterator.rb +305 -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 +2654 -38
- data/lib/carray/window_iterator.rb +927 -0
- data/lib/carray.rb +55 -57
- data/yard-stubs/ca_obj_array.rb +385 -0
- data/yard-stubs/ca_obj_bitarray.rb +38 -0
- data/yard-stubs/ca_obj_bitfield.rb +43 -0
- data/yard-stubs/ca_obj_block.rb +73 -0
- data/yard-stubs/ca_obj_byte_swap.rb +56 -0
- data/yard-stubs/ca_obj_fake.rb +31 -0
- data/yard-stubs/ca_obj_farray.rb +32 -0
- data/yard-stubs/ca_obj_field.rb +45 -0
- data/yard-stubs/ca_obj_grid.rb +35 -0
- data/yard-stubs/ca_obj_refer.rb +72 -0
- data/yard-stubs/ca_obj_roll.rb +45 -0
- data/yard-stubs/ca_obj_shift.rb +43 -0
- data/yard-stubs/ca_obj_stride.rb +181 -0
- data/yard-stubs/ca_obj_tile.rb +29 -0
- data/yard-stubs/ca_obj_transpose.rb +40 -0
- data/yard-stubs/ca_obj_window.rb +49 -0
- data/yard-stubs/carray_access.rb +131 -0
- data/yard-stubs/carray_attribute.rb +246 -0
- data/yard-stubs/carray_broadcast.rb +37 -0
- data/yard-stubs/carray_cast.rb +489 -0
- data/yard-stubs/carray_class.rb +65 -0
- data/yard-stubs/carray_conversion.rb +76 -0
- data/yard-stubs/carray_copy.rb +79 -0
- data/yard-stubs/carray_core.rb +114 -0
- data/yard-stubs/carray_count.rb +79 -0
- data/yard-stubs/carray_element.rb +108 -0
- data/yard-stubs/carray_generate.rb +66 -0
- data/yard-stubs/carray_lazy.rb +23 -0
- data/yard-stubs/carray_loop.rb +140 -0
- data/yard-stubs/carray_mask.rb +259 -0
- data/yard-stubs/carray_math.rb +132 -0
- data/yard-stubs/carray_mathfunc.rb +45 -0
- data/yard-stubs/carray_median_percentile.rb +89 -0
- data/yard-stubs/carray_memory_view.rb +163 -0
- data/yard-stubs/carray_order.rb +312 -0
- data/yard-stubs/carray_random.rb +89 -0
- data/yard-stubs/carray_scatter.rb +106 -0
- data/yard-stubs/carray_slab.rb +57 -0
- data/yard-stubs/carray_sort.rb +163 -0
- data/yard-stubs/carray_test.rb +85 -0
- data/yard-stubs/carray_undef.rb +64 -0
- data/yard-stubs/carray_utils.rb +97 -0
- data/yard-stubs/ruby_carray.rb +193 -0
- metadata +220 -138
- 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/ca_obj_unbound_repeat.c +0 -529
- 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/mailmap +0 -1
- 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/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,132 @@
|
|
|
1
|
+
# CAFrame row concatenation (memo §5, spine = addressing + gather).
|
|
2
|
+
#
|
|
3
|
+
# Two entry points, mirroring the CArray-level taxonomy:
|
|
4
|
+
#
|
|
5
|
+
# +CAFrame.meld+ view frame, strict same data type per column,
|
|
6
|
+
# each column is a CAMeld view over the inputs
|
|
7
|
+
# +CAFrame.concatenate+ eager frame, auto-casts per column,
|
|
8
|
+
# each column is a materialised entity
|
|
9
|
+
#
|
|
10
|
+
# Both are class methods (symmetric N-ary; no frame is privileged), like
|
|
11
|
+
# +CArray.meld+ / +CArray.concatenate+, not instance verbs.
|
|
12
|
+
|
|
13
|
+
class CAFrame
|
|
14
|
+
# Weld frames along the row axis, view-style. Each output column is
|
|
15
|
+
# +CArray.meld+ of that column across the input frames, so the result
|
|
16
|
+
# is a view frame that shares storage with the inputs (chain composability
|
|
17
|
+
# preserved: writes to the result flow back to whichever input frame owns
|
|
18
|
+
# the target segment, and vice versa).
|
|
19
|
+
#
|
|
20
|
+
# Per-column +data_type+ must match across frames (CArray.meld is a view
|
|
21
|
+
# constructor that refuses to auto-cast — silent promotion here would
|
|
22
|
+
# hide schema drift). For an eager, auto-casting alternative use
|
|
23
|
+
# {CAFrame.concatenate}.
|
|
24
|
+
#
|
|
25
|
+
# Column matching is by name (output order follows the first frame);
|
|
26
|
+
# every frame must carry the same column-name set. N-D columns carry
|
|
27
|
+
# their trailing dimensions along; masks are preserved.
|
|
28
|
+
#
|
|
29
|
+
# The index is welded too when every frame has one (their +axis_name+
|
|
30
|
+
# must agree); if none do, the result has no index; a mix raises.
|
|
31
|
+
# Column-set mismatch raises — a union-with-UNDEF mode is a possible
|
|
32
|
+
# future opt-in, kept out here to stay explicit (memo §4.2).
|
|
33
|
+
#
|
|
34
|
+
# CAFrame.meld(jan, feb, mar) # view over three months
|
|
35
|
+
# CAFrame.meld([jan, feb, mar]) # an Array is accepted too
|
|
36
|
+
def self.meld(*frames)
|
|
37
|
+
frames = frames.flatten
|
|
38
|
+
check_concat_inputs(frames, verb: "meld")
|
|
39
|
+
first = frames.first
|
|
40
|
+
names = first.variable_names
|
|
41
|
+
check_column_sets(frames, names, verb: "meld")
|
|
42
|
+
cols = {}
|
|
43
|
+
names.each do |name|
|
|
44
|
+
cols[name] = CArray.meld(frames.map { |f| f[name] }, axis: 0)
|
|
45
|
+
end
|
|
46
|
+
new(cols, axis_name: first.axis_name, index: meld_index(frames))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Concatenate frames along the row axis, eagerly. Each output column is
|
|
50
|
+
# +CArray.concatenate+ of that column across the input frames, so per-column
|
|
51
|
+
# data types auto-promote to a common type. The result is a fresh, independent
|
|
52
|
+
# frame — writes to it do not propagate back to the input frames.
|
|
53
|
+
#
|
|
54
|
+
# For a view frame that shares storage with the inputs (strict same data type
|
|
55
|
+
# per column, chain composability preserved) use {CAFrame.meld}.
|
|
56
|
+
#
|
|
57
|
+
# Column matching, index handling, and column-set / index-mix rules
|
|
58
|
+
# match {CAFrame.meld}.
|
|
59
|
+
#
|
|
60
|
+
# CAFrame.concatenate(jan, feb, mar) # eager, independent result
|
|
61
|
+
# CAFrame.concatenate([jan, feb, mar]) # an Array is accepted too
|
|
62
|
+
def self.concatenate(*frames)
|
|
63
|
+
frames = frames.flatten
|
|
64
|
+
check_concat_inputs(frames, verb: "concatenate")
|
|
65
|
+
first = frames.first
|
|
66
|
+
names = first.variable_names
|
|
67
|
+
check_column_sets(frames, names, verb: "concatenate")
|
|
68
|
+
cols = {}
|
|
69
|
+
names.each do |name|
|
|
70
|
+
cols[name] = CArray.concatenate(frames.map { |f| f[name] })
|
|
71
|
+
end
|
|
72
|
+
new(cols, axis_name: first.axis_name, index: concatenate_index(frames))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# ---- shared validators -------------------------------------------------
|
|
76
|
+
|
|
77
|
+
def self.check_concat_inputs(frames, verb:)
|
|
78
|
+
raise ArgumentError, "#{verb} requires at least one frame" if frames.empty?
|
|
79
|
+
unless frames.all? { |f| f.is_a?(CAFrame) }
|
|
80
|
+
raise ArgumentError, "#{verb} expects CAFrame arguments"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
private_class_method :check_concat_inputs
|
|
84
|
+
|
|
85
|
+
def self.check_column_sets(frames, names, verb:)
|
|
86
|
+
expected = names.sort
|
|
87
|
+
frames.each_with_index do |f, i|
|
|
88
|
+
next if i.zero?
|
|
89
|
+
if f.variable_names.sort != expected
|
|
90
|
+
raise ArgumentError,
|
|
91
|
+
"#{verb}: frame #{i} has columns #{f.variable_names.inspect}, " \
|
|
92
|
+
"expected the same set as #{names.inspect}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
private_class_method :check_column_sets
|
|
97
|
+
|
|
98
|
+
# ---- index helpers -----------------------------------------------------
|
|
99
|
+
|
|
100
|
+
# Index welded via CArray.meld (view). Every frame must agree: all
|
|
101
|
+
# indexed (axis names must match) or none.
|
|
102
|
+
def self.meld_index(frames)
|
|
103
|
+
indexes, axis = index_pieces(frames, verb: "meld")
|
|
104
|
+
return nil unless indexes
|
|
105
|
+
axis # unused; kept for symmetry
|
|
106
|
+
CArray.meld(indexes, axis: 0)
|
|
107
|
+
end
|
|
108
|
+
private_class_method :meld_index
|
|
109
|
+
|
|
110
|
+
# Index concatenated eagerly via CArray.concatenate.
|
|
111
|
+
def self.concatenate_index(frames)
|
|
112
|
+
indexes, _axis = index_pieces(frames, verb: "concatenate")
|
|
113
|
+
return nil unless indexes
|
|
114
|
+
CArray.concatenate(indexes)
|
|
115
|
+
end
|
|
116
|
+
private_class_method :concatenate_index
|
|
117
|
+
|
|
118
|
+
def self.index_pieces(frames, verb:)
|
|
119
|
+
indexes = frames.map(&:index)
|
|
120
|
+
return nil if indexes.none?
|
|
121
|
+
unless indexes.all?
|
|
122
|
+
raise ArgumentError, "#{verb}: some frames have an index and others do not"
|
|
123
|
+
end
|
|
124
|
+
axis_names = frames.map(&:axis_name).uniq
|
|
125
|
+
unless axis_names.size == 1
|
|
126
|
+
raise ArgumentError,
|
|
127
|
+
"#{verb}: indexed frames have different axis names #{axis_names.inspect}"
|
|
128
|
+
end
|
|
129
|
+
[indexes, axis_names.first]
|
|
130
|
+
end
|
|
131
|
+
private_class_method :index_pieces
|
|
132
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# CAFrame row iteration and matrix conversion (memo §11.9, §11.11).
|
|
2
|
+
|
|
3
|
+
class CAFrame
|
|
4
|
+
# Iterate rows as Ruby Hashes (memo §11.11). This is an escape path — the
|
|
5
|
+
# primary idiom is column-vectorized work (§4.3); each_row is for touching
|
|
6
|
+
# heterogeneous / Face-carrying rows now and then, not hot loops. Without a
|
|
7
|
+
# block, returns an Enumerator.
|
|
8
|
+
def each_row
|
|
9
|
+
return enum_for(:each_row) unless block_given?
|
|
10
|
+
nrow.times { |i| yield row(i) }
|
|
11
|
+
self
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Export rows as an Array of plain Ruby Hashes (memo §11.11) — the inverse of
|
|
15
|
+
# +from_records+ and the shape +JSON.generate+ wants. Each row is one Hash of
|
|
16
|
+
# column name => value; a scalar cell is a Ruby value, an N-D cell is a Ruby
|
|
17
|
+
# Array, and a masked cell (UNDEF) is +nil+. Normalizing UNDEF -> nil and
|
|
18
|
+
# CArray -> Array (unlike +each_row+, which yields the raw view with UNDEF /
|
|
19
|
+
# CArray slices) is what makes it round-trip: +from_records(df.to_records)+
|
|
20
|
+
# rebuilds the same typing, and the result is JSON-serializable.
|
|
21
|
+
def to_records
|
|
22
|
+
each_row.map { |row| row.transform_values { |v| record_value(v) } }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Hand the frame over as a CArray with the minimum work (memo §11.9): a
|
|
26
|
+
# 2-D **view** of shape (nrow, nvar), one column per variable in column
|
|
27
|
+
# order (§12-C). Nothing is materialised — the result is a +CAStack+ over
|
|
28
|
+
# the stored columns, so reads gather from them and writes flow back
|
|
29
|
+
# (§3.6). Call +copy+ on it for an independent, owned matrix; +CArray.tabulate+
|
|
30
|
+
# is the eager sibling that builds one directly.
|
|
31
|
+
#
|
|
32
|
+
# Only same-shape scalar (1-D) columns qualify. An N-D column has no single
|
|
33
|
+
# matrix form and raises — escape it per column with +df["name"]+. A mixed
|
|
34
|
+
# data type set is promoted to a common type (+result_type+, §12-F) through
|
|
35
|
+
# lazy cast lanes, so the promotion costs no buffer either.
|
|
36
|
+
#
|
|
37
|
+
# +writable: true+ demands a result whose writes reach this frame's own
|
|
38
|
+
# columns (the 3.0 +to_ca+ contract). That holds only when every column
|
|
39
|
+
# enters the stack unchanged, so it is refused when a column is read-only
|
|
40
|
+
# or when the common type promotes it: the promoted column is stacked
|
|
41
|
+
# through a cast lane, and a write there is no longer the value the caller
|
|
42
|
+
# handed over. Cast the frame first (+df.cast+) when the promoted matrix is
|
|
43
|
+
# what should be written to.
|
|
44
|
+
def to_ca(writable: false)
|
|
45
|
+
cols = @columns.values
|
|
46
|
+
raise ArgumentError, "frame has no columns to stack into a matrix" if cols.empty?
|
|
47
|
+
nd = @columns.find { |_, c| c.ndim != 1 }
|
|
48
|
+
if nd
|
|
49
|
+
raise ArgumentError,
|
|
50
|
+
"to_ca needs all-scalar (1-D) columns; #{nd.first.inspect} is " \
|
|
51
|
+
"#{nd.last.ndim}-D — escape per column with df[name]"
|
|
52
|
+
end
|
|
53
|
+
refuse_unshared_columns(cols) if writable
|
|
54
|
+
begin
|
|
55
|
+
CArray.stack(cols, axis: 1)
|
|
56
|
+
rescue ArgumentError, RuntimeError => e
|
|
57
|
+
# The columns have no common type (a text or Face column beside a
|
|
58
|
+
# numeric one). Point at the frame-level verb that gives them one.
|
|
59
|
+
raise e.class,
|
|
60
|
+
"#{e.message} -- df.promote(:object) brings every column to its " \
|
|
61
|
+
"surface values, and that frame stacks"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The +writable: true+ half of +to_ca+: raise unless the stack's parents are
|
|
66
|
+
# the stored columns themselves. +promote_list+ hands back each column
|
|
67
|
+
# untouched when no coercion is needed, so identity is the exact test for
|
|
68
|
+
# "this column is shared, not re-expressed".
|
|
69
|
+
private def refuse_unshared_columns(cols)
|
|
70
|
+
ro = @columns.find { |_, c| c.read_only? }
|
|
71
|
+
if ro
|
|
72
|
+
raise "CAFrame#to_ca cannot satisfy `writable: true': column " \
|
|
73
|
+
"#{ro.first.inspect} is read-only"
|
|
74
|
+
end
|
|
75
|
+
promoted = CArray.promote_list(cols)
|
|
76
|
+
recast = @columns.keys.zip(cols, promoted).find { |_, col, p| !p.equal?(col) }
|
|
77
|
+
if recast
|
|
78
|
+
raise "CAFrame#to_ca cannot satisfy `writable: true': column " \
|
|
79
|
+
"#{recast[0].inspect} (#{recast[1].data_type}) is promoted to " \
|
|
80
|
+
"#{recast[2].data_type} for the common type, so it is stacked " \
|
|
81
|
+
"through a cast lane; cast the frame first (df.cast) or take the " \
|
|
82
|
+
"column with df[name]"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private def record_value(v)
|
|
87
|
+
if UNDEF.equal?(v)
|
|
88
|
+
nil
|
|
89
|
+
elsif v.is_a?(CArray)
|
|
90
|
+
v.to_a.map { |e| UNDEF.equal?(e) ? nil : e }
|
|
91
|
+
else
|
|
92
|
+
v
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# A small, self-contained CSV tokenizer for CAFrame.from_csv.
|
|
2
|
+
#
|
|
3
|
+
# It produces raw String cells and does no type inference — casting is a
|
|
4
|
+
# separate step. The tokenizer is tuned for the
|
|
5
|
+
# common shape of scientific / ETL CSV: records with no quote character take a
|
|
6
|
+
# single String#split fast path, and only quote-bearing records fall back to
|
|
7
|
+
# the field scanner. Quoted fields carry embedded separators, embedded newlines
|
|
8
|
+
# (multi-line records) and doubled-quote ("") escapes.
|
|
9
|
+
#
|
|
10
|
+
# Correctness choices:
|
|
11
|
+
# * RFC 4180 spacing by default -- spaces are significant and preserved.
|
|
12
|
+
# Pass +strip: true+ to trim unquoted fields (handy for numeric columns).
|
|
13
|
+
# * An empty unquoted field is nil (missing); an empty quoted field ("") is
|
|
14
|
+
# the empty String. Both cast to UNDEF for numeric columns (parse-mask).
|
|
15
|
+
# * A UTF-8 BOM is stripped via the "bom|utf-8" read mode by default.
|
|
16
|
+
# * A parse error names the record number.
|
|
17
|
+
#
|
|
18
|
+
# Two entry points share the Tokenizer:
|
|
19
|
+
# * CSVParser.parse / parse_file -- whole-file parse into [headers, rows].
|
|
20
|
+
# * CSVReader -- the block reading-control DSL used by
|
|
21
|
+
# CAFrame.from_csv (header / skip / body /
|
|
22
|
+
# column_names).
|
|
23
|
+
|
|
24
|
+
require "strscan"
|
|
25
|
+
|
|
26
|
+
class CAFrame
|
|
27
|
+
# CSV tokenizer behind `CAFrame.from_csv`. It produces raw String cells
|
|
28
|
+
# and does no type inference — casting is a separate step.
|
|
29
|
+
#
|
|
30
|
+
# Records with no quote character take a `String#split` fast path; only
|
|
31
|
+
# quote-bearing records fall back to the field scanner, which handles
|
|
32
|
+
# embedded separators, embedded newlines and doubled-quote escapes.
|
|
33
|
+
# Spacing follows RFC 4180 (significant and preserved) unless `strip:` is
|
|
34
|
+
# given. An empty unquoted field is `nil` (missing); an empty quoted field
|
|
35
|
+
# is the empty String.
|
|
36
|
+
module CSVParser
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
# Raised when the input cannot be tokenized, e.g. a quoted field that
|
|
40
|
+
# never closes before end of input.
|
|
41
|
+
class MalformedCSV < StandardError; end
|
|
42
|
+
|
|
43
|
+
# Parse a file into [headers, rows]. +encoding+ is an IO open-mode encoding
|
|
44
|
+
# string; the default strips a leading BOM and reads UTF-8.
|
|
45
|
+
def parse_file(path, encoding: "bom|utf-8", **opts)
|
|
46
|
+
File.open(path, "r:#{encoding}") { |io| parse(io, **opts) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Parse an IO (or anything answering +gets+). The first record supplies the
|
|
50
|
+
# headers; the rest are data rows. Fully blank lines are skipped.
|
|
51
|
+
def parse(io, sep: ",", quote: '"', strip: false)
|
|
52
|
+
tok = Tokenizer.new(sep, quote, strip)
|
|
53
|
+
headers = nil
|
|
54
|
+
rows = []
|
|
55
|
+
while (fields = tok.read(io))
|
|
56
|
+
if headers.nil?
|
|
57
|
+
headers = fields.map(&:to_s)
|
|
58
|
+
else
|
|
59
|
+
rows << fields
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
[headers, rows]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Read one logical record, joining continuation lines while a quoted field
|
|
66
|
+
# is still open (an odd number of quote characters means unbalanced).
|
|
67
|
+
def read_record(io, quote)
|
|
68
|
+
line = io.gets
|
|
69
|
+
return nil if line.nil?
|
|
70
|
+
rec = line.dup
|
|
71
|
+
while rec.count(quote).odd?
|
|
72
|
+
more = io.gets
|
|
73
|
+
raise MalformedCSV, "unterminated quoted field at end of input" if more.nil?
|
|
74
|
+
rec << more
|
|
75
|
+
end
|
|
76
|
+
rec.chomp!
|
|
77
|
+
rec
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Splits records into fields. Regexps are compiled once and reused across
|
|
81
|
+
# every record, so per-row cost stays low.
|
|
82
|
+
class Tokenizer
|
|
83
|
+
def initialize(sep, quote, strip)
|
|
84
|
+
@sep = sep
|
|
85
|
+
@quote = quote
|
|
86
|
+
@strip = strip
|
|
87
|
+
@sep_re = /#{Regexp.escape(sep)}/
|
|
88
|
+
@quote_re = /#{Regexp.escape(quote)}/
|
|
89
|
+
@unquoted = /[^#{Regexp.escape(sep)}]*/
|
|
90
|
+
@inner = /[^#{Regexp.escape(quote)}]*/
|
|
91
|
+
@recno = 0
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Fields of the next non-blank record, or nil at EOF.
|
|
95
|
+
def read(io)
|
|
96
|
+
loop do
|
|
97
|
+
rec = CSVParser.read_record(io, @quote)
|
|
98
|
+
return nil if rec.nil?
|
|
99
|
+
@recno += 1
|
|
100
|
+
next if rec.empty?
|
|
101
|
+
return rec.count(@quote).zero? ? simple(rec) : scan(rec)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private def simple(rec)
|
|
106
|
+
rec.split(@sep, -1).map do |cell|
|
|
107
|
+
cell = cell.strip if @strip
|
|
108
|
+
cell.empty? ? nil : cell
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private def scan(rec)
|
|
113
|
+
sc = StringScanner.new(rec)
|
|
114
|
+
fields = []
|
|
115
|
+
loop do
|
|
116
|
+
if sc.scan(@quote_re)
|
|
117
|
+
buf = +""
|
|
118
|
+
loop do
|
|
119
|
+
buf << sc.scan(@inner)
|
|
120
|
+
unless sc.scan(@quote_re)
|
|
121
|
+
raise MalformedCSV, "unterminated quoted field in record #{@recno}"
|
|
122
|
+
end
|
|
123
|
+
if sc.scan(@quote_re) # doubled quote -> literal quote, field continues
|
|
124
|
+
buf << @quote
|
|
125
|
+
else
|
|
126
|
+
break
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
fields << buf
|
|
130
|
+
else
|
|
131
|
+
cell = sc.scan(@unquoted) || ""
|
|
132
|
+
cell = cell.strip if @strip
|
|
133
|
+
fields << (cell.empty? ? nil : cell)
|
|
134
|
+
end
|
|
135
|
+
break unless sc.scan(@sep_re)
|
|
136
|
+
end
|
|
137
|
+
fields
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The block reading-control DSL for CAFrame.from_csv. A file
|
|
143
|
+
# often has preamble lines, a units row, or no header at all; the block says,
|
|
144
|
+
# in order, how to consume the stream:
|
|
145
|
+
#
|
|
146
|
+
# CAFrame.from_csv(path) do
|
|
147
|
+
# skip 3 # drop 3 preamble lines
|
|
148
|
+
# header # next record supplies the column names
|
|
149
|
+
# skip 1 # drop a units row
|
|
150
|
+
# body # the rest are data rows
|
|
151
|
+
# end
|
|
152
|
+
#
|
|
153
|
+
# CAFrame.from_csv(path) do # headerless file
|
|
154
|
+
# column_names "date", "temp", "rh"
|
|
155
|
+
# body
|
|
156
|
+
# end
|
|
157
|
+
#
|
|
158
|
+
# The verbs are +skip+ / +header+ / +column_names+ / +body+; each returns a
|
|
159
|
+
# value useful inline (header returns its fields) and the ordering is the
|
|
160
|
+
# script. Without a block, from_csv runs the default +header+ then +body+.
|
|
161
|
+
class CSVReader
|
|
162
|
+
def initialize(io, sep: ",", quote: '"', strip: false)
|
|
163
|
+
@io = io
|
|
164
|
+
@tok = CSVParser::Tokenizer.new(sep, quote, strip)
|
|
165
|
+
@names = nil
|
|
166
|
+
@rows = nil
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Drop +n+ raw lines (preamble, units, notes).
|
|
170
|
+
def skip(n = 1)
|
|
171
|
+
n.times { @io.gets }
|
|
172
|
+
self
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Read one record. With no argument it becomes the column names; with a
|
|
176
|
+
# name it is a secondary header (e.g. units) -- read, returned, not used as
|
|
177
|
+
# names. Returns the record's fields either way.
|
|
178
|
+
def header(name = nil)
|
|
179
|
+
fields = @tok.read(@io)
|
|
180
|
+
raise CSVParser::MalformedCSV, "header expected but input ended" if fields.nil?
|
|
181
|
+
if name.nil?
|
|
182
|
+
@names = fields.map(&:to_s)
|
|
183
|
+
else
|
|
184
|
+
(@named_headers ||= {})[name.to_s] = fields
|
|
185
|
+
end
|
|
186
|
+
fields
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Set the column names explicitly (headerless files).
|
|
190
|
+
def column_names(*names)
|
|
191
|
+
@names = names.flatten.map(&:to_s)
|
|
192
|
+
self
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Consume the remaining records as data rows.
|
|
196
|
+
def body
|
|
197
|
+
rows = []
|
|
198
|
+
while (fields = @tok.read(@io))
|
|
199
|
+
rows << fields
|
|
200
|
+
end
|
|
201
|
+
@rows = rows
|
|
202
|
+
self
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# [names_or_nil, rows] for CAFrame.from_csv to build from. names is nil when
|
|
206
|
+
# neither header nor column_names ran (positional names are generated).
|
|
207
|
+
def result
|
|
208
|
+
[@names, @rows || []]
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|