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,186 @@
|
|
|
1
|
+
# CAFrame group_by (memo §5 spine, §11.6 surface).
|
|
2
|
+
#
|
|
3
|
+
# Grouping is always on the row axis (axis 0). The key is any length-N
|
|
4
|
+
# thing: a column name, several column names (composite key), or an
|
|
5
|
+
# external length-N CArray. Everything routes through categorize -> codes
|
|
6
|
+
# -> group_by_category, so the frame layer only builds the key and hands
|
|
7
|
+
# the group iterator back (exposure, §4.3).
|
|
8
|
+
|
|
9
|
+
class CAFrame
|
|
10
|
+
# Group rows by one or more keys. Each key is a column name (String) or an
|
|
11
|
+
# external length-N CArray. Returns a GroupedFrame.
|
|
12
|
+
def group_by(*keys)
|
|
13
|
+
raise ArgumentError, "group_by needs at least one key" if keys.empty?
|
|
14
|
+
cat = grouping_categorical(keys)
|
|
15
|
+
axis = keys.size == 1 && keys.first.is_a?(String) ? keys.first : "group"
|
|
16
|
+
GroupedFrame.new(self, cat, axis)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Number of rows currently selected — used by group per-group view-frames
|
|
20
|
+
# and elsewhere; already provided by the core (attr_reader :nrow).
|
|
21
|
+
|
|
22
|
+
private def grouping_categorical(keys)
|
|
23
|
+
cols = keys.map { |k| key_column(k) }
|
|
24
|
+
if cols.size == 1
|
|
25
|
+
cols.first.categorize
|
|
26
|
+
else
|
|
27
|
+
# Composite key: one object cell per row holding the tuple of key
|
|
28
|
+
# values, categorized by content (the codes are composed from the
|
|
29
|
+
# per-column keys).
|
|
30
|
+
n = nrow
|
|
31
|
+
CArray.object(n) { |i| cols.map { |c| c[i] } }.categorize
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private def key_column(key)
|
|
36
|
+
case key
|
|
37
|
+
when String
|
|
38
|
+
self[key]
|
|
39
|
+
when CArray
|
|
40
|
+
unless key.shape[0] == nrow
|
|
41
|
+
raise ArgumentError,
|
|
42
|
+
"external group key length #{key.shape[0]} != nrow #{nrow}"
|
|
43
|
+
end
|
|
44
|
+
key
|
|
45
|
+
else
|
|
46
|
+
raise ArgumentError, "group key must be a column name or CArray (got #{key.class})"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# GroupedFrame — the result of +CAFrame#group_by+ (memo §11.6, §16).
|
|
52
|
+
#
|
|
53
|
+
# Holds the grouping categorical once (grouping is per-key, computed once
|
|
54
|
+
# and shared across every column). Three surfaces:
|
|
55
|
+
# grp["col"] -> the CArray group iterator (raw exposure)
|
|
56
|
+
# aggregate -> declarative per-column reductions into a new frame
|
|
57
|
+
# table { |g| }-> cross-column Ruby escape, g is a per-group view-frame
|
|
58
|
+
class GroupedFrame
|
|
59
|
+
def initialize(frame, cat, axis_name)
|
|
60
|
+
@frame = frame
|
|
61
|
+
@cat = cat
|
|
62
|
+
@axis_name = axis_name
|
|
63
|
+
@labels = cat.labels # group values, in code order
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Number of groups.
|
|
67
|
+
def ngroup
|
|
68
|
+
@labels.size
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Group key values (one per group), as an Array.
|
|
72
|
+
def labels
|
|
73
|
+
@labels.dup
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Raw exposure: the CArray group iterator for one column (memo §11.6).
|
|
77
|
+
def [](name)
|
|
78
|
+
@frame[name].group_by_category(@cat)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Declarative aggregation (memo §11.6). Spec maps an output column name to
|
|
82
|
+
# +[input_column, reduction]+, where reduction is a Symbol (vectorized,
|
|
83
|
+
# applied through the group iterator) or a Proc (per-group custom, called
|
|
84
|
+
# with the group's column slice).
|
|
85
|
+
def aggregate(spec)
|
|
86
|
+
cols = {}
|
|
87
|
+
spec.each do |out_name, (in_name, reduction)|
|
|
88
|
+
out = out_name.to_s
|
|
89
|
+
cols[out] =
|
|
90
|
+
case reduction
|
|
91
|
+
when Symbol
|
|
92
|
+
self[in_name].public_send(reduction)
|
|
93
|
+
when Proc
|
|
94
|
+
per_group_column(in_name, reduction)
|
|
95
|
+
else
|
|
96
|
+
raise ArgumentError,
|
|
97
|
+
"reduction must be a Symbol or Proc (got #{reduction.class})"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
CAFrame.new(cols, axis_name: @axis_name, index: label_index)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Cross-column Ruby escape (memo §11.6). The block receives a per-group
|
|
104
|
+
# view-frame and returns a Hash of output-name => value; the values are
|
|
105
|
+
# collected column-wise across groups into a new frame.
|
|
106
|
+
def table
|
|
107
|
+
collected = {}
|
|
108
|
+
order = nil
|
|
109
|
+
each_group_frame do |g|
|
|
110
|
+
out = yield(g)
|
|
111
|
+
unless out.is_a?(Hash)
|
|
112
|
+
raise ArgumentError, "table block must return a Hash (got #{out.class})"
|
|
113
|
+
end
|
|
114
|
+
order ||= out.keys.map(&:to_s)
|
|
115
|
+
out.each { |k, v| (collected[k.to_s] ||= []) << v }
|
|
116
|
+
end
|
|
117
|
+
cols = {}
|
|
118
|
+
(order || []).each do |name|
|
|
119
|
+
vals = collected[name]
|
|
120
|
+
cols[name] = CArray.object(vals.size) { |i| vals[i] }
|
|
121
|
+
end
|
|
122
|
+
CAFrame.new(cols, axis_name: @axis_name, index: label_index)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Convenience reductions over every numeric scalar column (memo §6-4
|
|
126
|
+
# "grp.mean"). Non-numeric / N-D columns are skipped.
|
|
127
|
+
[:sum, :mean, :min, :max].each do |red|
|
|
128
|
+
define_method(red) { reduce_numeric(red) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @return [String]
|
|
132
|
+
def inspect
|
|
133
|
+
"#<GroupedFrame ngroup=#{ngroup} by=#{@axis_name.inspect}>"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private def label_index
|
|
137
|
+
CArray.object(@labels.size) { |i| @labels[i] }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
NON_NUMERIC = [:object, :boolean, :fixlen].freeze
|
|
141
|
+
private_constant :NON_NUMERIC
|
|
142
|
+
|
|
143
|
+
private def reduce_numeric(reduction)
|
|
144
|
+
cols = {}
|
|
145
|
+
@frame.variable_names.each do |name|
|
|
146
|
+
col = @frame[name]
|
|
147
|
+
next unless col.ndim == 1 && !NON_NUMERIC.include?(col.data_type)
|
|
148
|
+
cols[name] = col.group_by_category(@cat).public_send(reduction)
|
|
149
|
+
end
|
|
150
|
+
CAFrame.new(cols, axis_name: @axis_name, index: label_index)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private def per_group_column(in_name, proc)
|
|
154
|
+
results = []
|
|
155
|
+
each_group_slice(in_name) { |slice| results << proc.call(slice) }
|
|
156
|
+
CArray.object(results.size) { |i| results[i] }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private def each_group_slice(in_name)
|
|
160
|
+
col = @frame[in_name]
|
|
161
|
+
ngroup.times do |k|
|
|
162
|
+
yield col[group_address(k), *([nil] * (col.ndim - 1))]
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private def each_group_frame
|
|
167
|
+
ngroup.times { |k| yield @frame[group_address(k)] }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private def group_address(k)
|
|
171
|
+
start = group_offsets[k]
|
|
172
|
+
group_perm[start...(start + group_sizes[k])]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private def group_perm
|
|
176
|
+
@group_perm ||= @cat.sort_addr
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private def group_offsets
|
|
180
|
+
@group_offsets ||= @cat.reduceat_index
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
private def group_sizes
|
|
184
|
+
@group_sizes ||= @cat.category_sizes
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# CAFrame CSV input/output (memo §11.2, §6-2).
|
|
2
|
+
|
|
3
|
+
require "carray/frame/csv_parser"
|
|
4
|
+
|
|
5
|
+
class CAFrame
|
|
6
|
+
# Read a CSV into a frame. The header row supplies column names (Strings,
|
|
7
|
+
# §3.7); every column is built raw as an object CArray of the cell strings
|
|
8
|
+
# (§4.2 -- read and arrange only, no type-inference engine). Casting is a
|
|
9
|
+
# separate step: pass +types:+ ({ "temp" => :float64 }, or the array-key /
|
|
10
|
+
# reverse forms of +cast+) to cast named columns on load, or call +cast+
|
|
11
|
+
# later. Broken cells fail to_type and become UNDEF automatically
|
|
12
|
+
# (parse-mask, §6-2).
|
|
13
|
+
#
|
|
14
|
+
# Parsing uses the built-in fast tokenizer (CSVParser). Options:
|
|
15
|
+
# sep: field separator (default ",")
|
|
16
|
+
# quote: quote character (default '"')
|
|
17
|
+
# strip: trim spaces from unquoted fields (default false, RFC spacing)
|
|
18
|
+
# +encoding+: IO open-mode encoding (default "bom|utf-8", strips a BOM)
|
|
19
|
+
# parser: a callable path -> [headers, rows] to inject another parser
|
|
20
|
+
# (e.g. the stdlib +csv+, or a typed-table source); when given,
|
|
21
|
+
# sep/quote/strip/encoding and any block are that parser's concern.
|
|
22
|
+
#
|
|
23
|
+
# A block gives reading control for files with preamble lines, a units row,
|
|
24
|
+
# or no header (memo §11.2), using +skip+ / +header+ / +column_names+ /
|
|
25
|
+
# +body+ (see CSVReader). Without a block the default is +header+ then +body+.
|
|
26
|
+
#
|
|
27
|
+
# CAFrame.from_csv("obs.csv") do
|
|
28
|
+
# skip 2; header; skip 1; body
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# Columns are handed to the frame as CABlock views over one backing object
|
|
32
|
+
# array (§3.6 view-by-default); casting a column materializes it, and +copy+
|
|
33
|
+
# gives an independent frame.
|
|
34
|
+
def self.from_csv(path, types: nil,
|
|
35
|
+
sep: ",", quote: '"', strip: false,
|
|
36
|
+
encoding: "bom|utf-8", parser: nil, &block)
|
|
37
|
+
names, rows =
|
|
38
|
+
if parser
|
|
39
|
+
parser.call(path)
|
|
40
|
+
else
|
|
41
|
+
File.open(path, "r:#{encoding}") do |io|
|
|
42
|
+
reader = CSVReader.new(io, sep: sep, quote: quote, strip: strip)
|
|
43
|
+
if block
|
|
44
|
+
block.arity == 1 ? block.call(reader) : reader.instance_exec(&block)
|
|
45
|
+
else
|
|
46
|
+
reader.header
|
|
47
|
+
reader.body
|
|
48
|
+
end
|
|
49
|
+
reader.result
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
frame = build_frame(names, rows)
|
|
54
|
+
frame.cast(types) if types
|
|
55
|
+
frame
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Build a frame from parsed [names, rows]. When names is nil (headerless and
|
|
59
|
+
# no column_names) positional names "c0".."cN" are generated from the widest
|
|
60
|
+
# row. Rows are squared off to the column count (short rows padded with nil,
|
|
61
|
+
# over-long rows raise), one 2-D object array is bulk-filled, and each column
|
|
62
|
+
# is a view into it (§3.6).
|
|
63
|
+
# Write the frame as CSV. CSV is a flat table of scalar cells, so this is the
|
|
64
|
+
# text form of the same all-scalar subset +to_ca+ requires (§11.9): every
|
|
65
|
+
# column must be 1-D. Unlike +to_ca+ it does not promote to a common data type --
|
|
66
|
+
# each column is formatted to text independently, so mixed data types (numbers,
|
|
67
|
+
# strings, datetime / categorical Faces) sit side by side. An N-D column has
|
|
68
|
+
# no flat CSV cell and raises; export it per column, or use +to_records+ +
|
|
69
|
+
# JSON for the structured shape (memo §11.9, the N-D escape).
|
|
70
|
+
#
|
|
71
|
+
# With +path+, writes the file and returns self; without it, returns the CSV
|
|
72
|
+
# String. The index (if any) is written as the first column under +axis_name+
|
|
73
|
+
# unless +index: false+. A masked cell (UNDEF) becomes an empty field, which
|
|
74
|
+
# +from_csv+ reads back as UNDEF (parse-mask, §6-2) -- so mask round-trips. A
|
|
75
|
+
# genuine empty string is written quoted (+""+) to stay distinct from missing,
|
|
76
|
+
# matching the tokenizer's own unquoted-empty vs quoted-empty distinction.
|
|
77
|
+
#
|
|
78
|
+
# df.to_csv("out.csv") # write file
|
|
79
|
+
# csv = df.to_csv # get a String
|
|
80
|
+
#
|
|
81
|
+
# Options: +sep+ / +quote+ mirror +from_csv+; +header+ writes the name row
|
|
82
|
+
# (default true); +index+ writes the index column (default true).
|
|
83
|
+
def to_csv(path = nil, sep: ",", quote: '"', header: true, index: true)
|
|
84
|
+
nd = @columns.find { |_, c| c.ndim != 1 }
|
|
85
|
+
if nd
|
|
86
|
+
raise ArgumentError,
|
|
87
|
+
"to_csv needs all-scalar (1-D) columns; #{nd.first.inspect} is " \
|
|
88
|
+
"#{nd.last.ndim}-D — export it per column or via to_records + JSON"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
names = []
|
|
92
|
+
formatted = []
|
|
93
|
+
if index && @index
|
|
94
|
+
names << @axis_name
|
|
95
|
+
formatted << format_csv_column(@index)
|
|
96
|
+
end
|
|
97
|
+
@columns.each do |name, col|
|
|
98
|
+
names << name
|
|
99
|
+
formatted << format_csv_column(col)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
out = +""
|
|
103
|
+
if header
|
|
104
|
+
out << names.map { |t| quote_csv_field(t, sep, quote) }.join(sep) << "\n"
|
|
105
|
+
end
|
|
106
|
+
@nrow.times do |i|
|
|
107
|
+
out << formatted.map { |fcol| quote_csv_field(fcol[i], sep, quote) }.join(sep) << "\n"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if path
|
|
111
|
+
File.write(path, out)
|
|
112
|
+
self
|
|
113
|
+
else
|
|
114
|
+
out
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Render the frame as an aligned text table for reading:
|
|
119
|
+
#
|
|
120
|
+
# puts df.to_table
|
|
121
|
+
#
|
|
122
|
+
# time temp station
|
|
123
|
+
# ---------- ---- -------
|
|
124
|
+
# 2026-01-01 1.5 Tokyo
|
|
125
|
+
# 2026-01-02 _ Osaka
|
|
126
|
+
#
|
|
127
|
+
# This is the display counterpart of +to_csv+ and shares none of its
|
|
128
|
+
# constraints: it is text meant to be looked at, not read back. Numeric
|
|
129
|
+
# columns are right-aligned, everything else left-aligned; a masked cell
|
|
130
|
+
# shows as +_+, the same marker CArray's own inspect uses. An N-D column
|
|
131
|
+
# (which +to_csv+ rejects, having no flat cell) shows each row's slice as
|
|
132
|
+
# an Array literal.
|
|
133
|
+
#
|
|
134
|
+
# Float cells are rounded to +precision+ decimal places for display only
|
|
135
|
+
# (default 6); +precision: nil+ prints them at full precision, which is
|
|
136
|
+
# faithful but lets one long value set the column width.
|
|
137
|
+
#
|
|
138
|
+
# Long frames are truncated in the middle: +rows+ caps how many rows are
|
|
139
|
+
# printed (default 20, split evenly around an ellipsis row), and
|
|
140
|
+
# +rows: nil+ prints every row. +index: false+ drops the index column.
|
|
141
|
+
def to_table(rows: 20, index: true, precision: 6)
|
|
142
|
+
head = rows && (rows + 1) / 2
|
|
143
|
+
render_table(head: head, tail: rows && rows - head,
|
|
144
|
+
index: index, precision: precision, footer: true)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# +to_s+ is the whole frame, +inspect+ the middle-elided one -- so +puts df+
|
|
148
|
+
# dumps everything and +p df+ stays a screenful. +inspect+ leads with the
|
|
149
|
+
# same summary line it always had (nrow, variable data types, index), so the
|
|
150
|
+
# table under it needs no row-count footer.
|
|
151
|
+
def to_s
|
|
152
|
+
to_table(rows: nil)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# The summary line (nrow, variable data types, index) followed by the
|
|
156
|
+
# middle-elided table.
|
|
157
|
+
# @return [String]
|
|
158
|
+
def inspect
|
|
159
|
+
parts = @columns.map { |k, v| "#{k}:#{v.data_type}#{v.ndim > 1 ? v.shape[1..].inspect : ''}" }
|
|
160
|
+
idx = @index ? " index=#{@axis_name.inspect}" : ""
|
|
161
|
+
head = "#<CAFrame nrow=#{@nrow} vars=[#{parts.join(', ')}]#{idx}>"
|
|
162
|
+
return head if @columns.empty?
|
|
163
|
+
head + "\n" + render_table(head: 8, tail: 2, index: true, precision: 6,
|
|
164
|
+
footer: false)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private def render_table(head:, tail:, index:, precision:, footer:)
|
|
168
|
+
names = []
|
|
169
|
+
columns = []
|
|
170
|
+
aligns = []
|
|
171
|
+
|
|
172
|
+
if index && @index
|
|
173
|
+
names << @axis_name
|
|
174
|
+
columns << @index
|
|
175
|
+
aligns << (@index.numeric? ? :right : :left)
|
|
176
|
+
end
|
|
177
|
+
@columns.each do |name, col|
|
|
178
|
+
names << name
|
|
179
|
+
columns << col
|
|
180
|
+
aligns << (col.ndim == 1 && col.numeric? ? :right : :left)
|
|
181
|
+
end
|
|
182
|
+
return "" if names.empty?
|
|
183
|
+
|
|
184
|
+
positions = table_row_positions(head, tail)
|
|
185
|
+
body = positions.map do |i|
|
|
186
|
+
if i.nil?
|
|
187
|
+
Array.new(columns.size, ":") # vertical ellipsis for the elided middle
|
|
188
|
+
else
|
|
189
|
+
columns.map { |col| format_table_cell(col, i, precision) }
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
widths = names.each_with_index.map do |name, j|
|
|
194
|
+
[display_width(name), *body.map { |cells| display_width(cells[j]) }].max
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
out = +""
|
|
198
|
+
out << table_row(names, widths, aligns) << "\n"
|
|
199
|
+
out << table_row(widths.map { |w| "-" * w }, widths, aligns) << "\n"
|
|
200
|
+
body.each { |cells| out << table_row(cells, widths, aligns) << "\n" }
|
|
201
|
+
if footer && positions.size - positions.count(nil) < @nrow
|
|
202
|
+
out << "(#{plural(@nrow, 'row')}, #{plural(@columns.size, 'variable')})\n"
|
|
203
|
+
end
|
|
204
|
+
out
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Row indices to print, with nil marking the elided middle. A nil head means
|
|
208
|
+
# no cap; a frame that already fits in head + tail is listed whole.
|
|
209
|
+
private def table_row_positions(head, tail)
|
|
210
|
+
return (0...@nrow).to_a if head.nil? || @nrow <= head + tail
|
|
211
|
+
(0...head).to_a + [nil] + ((@nrow - tail)...@nrow).to_a
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
private def table_row(cells, widths, aligns)
|
|
215
|
+
line = cells.each_with_index.map do |text, j|
|
|
216
|
+
pad = " " * (widths[j] - display_width(text))
|
|
217
|
+
aligns[j] == :right ? pad + text : text + pad
|
|
218
|
+
end.join(" ")
|
|
219
|
+
line.rstrip
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Column widths are counted in terminal cells, not characters: a CJK
|
|
223
|
+
# ideograph, kana, or full-width form occupies two cells, so counting
|
|
224
|
+
# characters would leave every column holding such a name ragged. The
|
|
225
|
+
# ranges below are the East Asian Wide / Fullwidth blocks; a combining
|
|
226
|
+
# mark takes no cell of its own.
|
|
227
|
+
WIDE_CHAR_RANGES = [
|
|
228
|
+
0x1100..0x115F, 0x2E80..0x303E, 0x3041..0x33FF, 0x3400..0x4DBF,
|
|
229
|
+
0x4E00..0x9FFF, 0xA000..0xA4CF, 0xA960..0xA97F, 0xAC00..0xD7A3,
|
|
230
|
+
0xF900..0xFAFF, 0xFE10..0xFE19, 0xFE30..0xFE6F, 0xFF00..0xFF60,
|
|
231
|
+
0xFFE0..0xFFE6, 0x1F300..0x1F64F, 0x1F900..0x1F9FF, 0x20000..0x3FFFD,
|
|
232
|
+
].freeze
|
|
233
|
+
private_constant :WIDE_CHAR_RANGES
|
|
234
|
+
|
|
235
|
+
COMBINING_RANGES = [0x0300..0x036F, 0x1AB0..0x1AFF, 0x20D0..0x20F0].freeze
|
|
236
|
+
private_constant :COMBINING_RANGES
|
|
237
|
+
|
|
238
|
+
private def display_width(text)
|
|
239
|
+
text.each_char.sum do |ch|
|
|
240
|
+
cp = ch.ord
|
|
241
|
+
if COMBINING_RANGES.any? { |r| r.cover?(cp) }
|
|
242
|
+
0
|
|
243
|
+
elsif WIDE_CHAR_RANGES.any? { |r| r.cover?(cp) }
|
|
244
|
+
2
|
|
245
|
+
else
|
|
246
|
+
1
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
private def plural(n, noun)
|
|
252
|
+
"#{n} #{noun}#{n == 1 ? '' : 's'}"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
private def format_table_cell(col, i, precision)
|
|
256
|
+
e = elem_at(col, i)
|
|
257
|
+
if UNDEF.equal?(e) || e.nil?
|
|
258
|
+
"_"
|
|
259
|
+
elsif e.is_a?(Float) && precision
|
|
260
|
+
# Display rounding only: a full-precision float (141.67833333333334)
|
|
261
|
+
# sets the column width for every other row and makes the table hard
|
|
262
|
+
# to read. precision: nil prints the value as Ruby renders it.
|
|
263
|
+
e.round(precision).to_s
|
|
264
|
+
elsif e.is_a?(CArray)
|
|
265
|
+
e.to_a.inspect
|
|
266
|
+
elsif e.is_a?(String)
|
|
267
|
+
e
|
|
268
|
+
elsif e.respond_to?(:iso8601)
|
|
269
|
+
e.iso8601
|
|
270
|
+
else
|
|
271
|
+
e.to_s
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
private def format_csv_column(col)
|
|
276
|
+
col.to_a.map do |e|
|
|
277
|
+
if UNDEF.equal?(e) || e.nil?
|
|
278
|
+
nil
|
|
279
|
+
elsif e.is_a?(String)
|
|
280
|
+
e
|
|
281
|
+
elsif e.respond_to?(:iso8601)
|
|
282
|
+
e.iso8601
|
|
283
|
+
else
|
|
284
|
+
e.to_s
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
private def quote_csv_field(text, sep, quote)
|
|
290
|
+
return "" if text.nil?
|
|
291
|
+
if text.empty? || text.include?(sep) || text.include?(quote) ||
|
|
292
|
+
text.include?("\n") || text.include?("\r")
|
|
293
|
+
quote + text.gsub(quote, quote * 2) + quote
|
|
294
|
+
else
|
|
295
|
+
text
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def self.build_frame(names, rows)
|
|
300
|
+
ncol = names ? names.size : (rows.map(&:size).max || 0)
|
|
301
|
+
names ||= Array.new(ncol) { |j| "c#{j}" }
|
|
302
|
+
|
|
303
|
+
cols = {}
|
|
304
|
+
if rows.empty?
|
|
305
|
+
names.each { |name| cols[name] = CArray.object(0) }
|
|
306
|
+
return new(cols)
|
|
307
|
+
end
|
|
308
|
+
rows.each_with_index do |r, i|
|
|
309
|
+
if r.size < ncol
|
|
310
|
+
r.concat(Array.new(ncol - r.size))
|
|
311
|
+
elsif r.size > ncol
|
|
312
|
+
raise ArgumentError,
|
|
313
|
+
"row #{i + 1} has #{r.size} fields, expected #{ncol}"
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
table = CArray.object(rows.size, ncol) { rows }
|
|
317
|
+
names.each_with_index { |name, j| cols[name] = table[nil, j] }
|
|
318
|
+
new(cols)
|
|
319
|
+
end
|
|
320
|
+
private_class_method :build_frame
|
|
321
|
+
end
|