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.
Files changed (386) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -25
  3. data/CHANGELOG.md +338 -0
  4. data/{NEWS.md → CHANGELOG.v1.md} +3 -0
  5. data/LICENSE +1 -1
  6. data/README.md +120 -36
  7. data/carray.gemspec +32 -30
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1244 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +124 -0
  15. data/ext/ca_binop_dispatch.h +152 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +239 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4367 -0
  26. data/ext/ca_kernel_iterator.h +2596 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +839 -0
  33. data/ext/ca_obj_binop.c +948 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +967 -0
  39. data/ext/ca_obj_face.c +750 -0
  40. data/ext/ca_obj_face.h +279 -0
  41. data/ext/ca_obj_fake.c +239 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +308 -0
  45. data/ext/ca_obj_grid.c +866 -440
  46. data/ext/ca_obj_meld.c +1039 -0
  47. data/ext/ca_obj_moncmp.c +588 -0
  48. data/ext/ca_obj_monop.c +1123 -0
  49. data/ext/ca_obj_object.c +866 -296
  50. data/ext/ca_obj_record.c +470 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +593 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +624 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1306 -0
  58. data/ext/ca_obj_shift.c +231 -793
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2584 -0
  62. data/ext/ca_obj_string.c +270 -0
  63. data/ext/ca_obj_tile.c +622 -0
  64. data/ext/ca_obj_time.c +548 -0
  65. data/ext/ca_obj_timedelta.c +437 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +785 -0
  68. data/ext/ca_obj_window.c +1202 -565
  69. data/ext/ca_op_byte_swap.c +175 -0
  70. data/ext/ca_op_cmplx64.h +123 -0
  71. data/ext/ca_op_ipower.c +316 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +473 -0
  75. data/ext/ca_sweep_engine.h +166 -0
  76. data/ext/ca_transform_common.c +235 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +810 -420
  80. data/ext/carray_access.c +873 -731
  81. data/ext/carray_attribute.c +98 -329
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +376 -0
  84. data/ext/carray_build_flags.h +3 -0
  85. data/ext/carray_call_cfunc.c +2897 -874
  86. data/ext/carray_call_cfunc.h +313 -0
  87. data/ext/carray_cast.c +1264 -315
  88. data/ext/carray_cast_func.rb +81 -40
  89. data/ext/carray_class.c +53 -63
  90. data/ext/carray_config.h +28 -0
  91. data/ext/carray_conversion.c +350 -346
  92. data/ext/carray_copy.c +168 -270
  93. data/ext/carray_core.c +1396 -206
  94. data/ext/carray_count.c +312 -0
  95. data/ext/carray_data_type.c +43 -19
  96. data/ext/carray_element.c +585 -213
  97. data/ext/carray_factorize.c +2542 -0
  98. data/ext/carray_generate.c +230 -559
  99. data/ext/carray_histogram.c +490 -0
  100. data/ext/carray_hold.c +228 -0
  101. data/ext/carray_index_classifier.c +1021 -0
  102. data/ext/carray_index_classifier.h +27 -0
  103. data/ext/carray_internal.h +136 -0
  104. data/ext/carray_kernels_bincmp.c +4446 -0
  105. data/ext/carray_kernels_binop.c +11001 -0
  106. data/ext/carray_kernels_init.c +1131 -0
  107. data/ext/carray_kernels_map.c +3467 -0
  108. data/ext/carray_kernels_moncmp.c +2097 -0
  109. data/ext/carray_kernels_monop.c +18313 -0
  110. data/ext/carray_kernels_reduce_aggregate.c +25837 -0
  111. data/ext/carray_kernels_reduce_boolean.c +330 -0
  112. data/ext/carray_kernels_reduce_cumulative.c +14593 -0
  113. data/ext/carray_kernels_reduce_extreme.c +16948 -0
  114. data/ext/carray_kernels_reduce_variance.c +3910 -0
  115. data/ext/carray_kernels_scan.c +3693 -0
  116. data/ext/carray_kernels_search.c +32138 -0
  117. data/ext/carray_kernels_sort.c +10626 -0
  118. data/ext/carray_kernels_triop.c +1392 -0
  119. data/ext/carray_lazy.c +737 -0
  120. data/ext/carray_loop.c +88 -200
  121. data/ext/carray_mask.c +853 -158
  122. data/ext/carray_math_kernel.h +120 -0
  123. data/ext/carray_mathfunc.c +10 -241
  124. data/ext/carray_median_percentile.c +1257 -0
  125. data/ext/carray_memory_view.c +1650 -0
  126. data/ext/carray_operator.c +1525 -320
  127. data/ext/carray_order.c +664 -1394
  128. data/ext/carray_partition.c +416 -0
  129. data/ext/carray_random.c +518 -0
  130. data/ext/carray_scatter.c +357 -0
  131. data/ext/carray_slab.c +1219 -0
  132. data/ext/carray_slab.h +84 -0
  133. data/ext/carray_sort.c +829 -0
  134. data/ext/carray_sort_kernel.c +620 -0
  135. data/ext/carray_struct.c +695 -0
  136. data/ext/carray_test.c +343 -229
  137. data/ext/carray_undef.c +34 -17
  138. data/ext/carray_utils.c +175 -74
  139. data/ext/extconf.rb +234 -55
  140. data/ext/mk_call_cfunc.rb +671 -0
  141. data/ext/mkkernel.rb +9096 -0
  142. data/ext/ruby_carray.c +211 -108
  143. data/ext/version.h +4 -14
  144. data/ext/version.rb +5 -13
  145. data/lib/carray/arrow_tensor.rb +401 -0
  146. data/lib/carray/attribute.rb +166 -0
  147. data/lib/carray/autoload_carray.rb +239 -0
  148. data/lib/carray/autoload_method_extension.rb +45 -0
  149. data/lib/carray/axis_group.rb +711 -0
  150. data/lib/carray/basics.rb +481 -0
  151. data/lib/carray/bincount_nd.rb +358 -0
  152. data/lib/carray/block_iterator.rb +614 -0
  153. data/lib/carray/boolean_reduce.rb +109 -0
  154. data/lib/carray/categorical.rb +561 -0
  155. data/lib/carray/categorical_iterator.rb +1084 -0
  156. data/lib/carray/complex.rb +150 -0
  157. data/lib/carray/conditional.rb +216 -0
  158. data/lib/carray/const_string.rb +228 -0
  159. data/lib/carray/construct.rb +160 -328
  160. data/lib/carray/core_extensions.rb +297 -0
  161. data/lib/carray/data_type_extension.rb +250 -0
  162. data/lib/carray/fixlen_string.rb +95 -0
  163. data/lib/carray/frame/concat.rb +132 -0
  164. data/lib/carray/frame/convert.rb +95 -0
  165. data/lib/carray/frame/csv_parser.rb +211 -0
  166. data/lib/carray/frame/frame.rb +642 -0
  167. data/lib/carray/frame/group.rb +186 -0
  168. data/lib/carray/frame/io.rb +321 -0
  169. data/lib/carray/frame/join.rb +248 -0
  170. data/lib/carray/frame/records.rb +99 -0
  171. data/lib/carray/frame/sort.rb +113 -0
  172. data/lib/carray/frame/verbs.rb +316 -0
  173. data/lib/carray/frame.rb +16 -0
  174. data/lib/carray/fuse_source.rb +123 -0
  175. data/lib/carray/fusion.rb +218 -0
  176. data/lib/carray/histogram.rb +512 -0
  177. data/lib/carray/inspect.rb +37 -26
  178. data/lib/carray/iterator.rb +58 -349
  179. data/lib/carray/lazy.rb +941 -0
  180. data/lib/carray/mask_gap_fill.rb +200 -0
  181. data/lib/carray/math.rb +78 -342
  182. data/lib/carray/meld_reduce.rb +289 -0
  183. data/lib/carray/methods/align_addr.rb +116 -0
  184. data/lib/carray/methods/bin.rb +128 -0
  185. data/lib/carray/methods/bincount.rb +87 -0
  186. data/lib/carray/methods/bit_string.rb +92 -0
  187. data/lib/carray/methods/broadcast.rb +63 -0
  188. data/lib/carray/methods/choose.rb +39 -0
  189. data/lib/carray/methods/composition.rb +280 -0
  190. data/lib/carray/methods/gather_nd.rb +206 -0
  191. data/lib/carray/methods/index.rb +39 -0
  192. data/lib/carray/methods/insert_block.rb +99 -0
  193. data/lib/carray/methods/is_in.rb +141 -0
  194. data/lib/carray/methods/join.rb +90 -0
  195. data/lib/carray/methods/locate_addr.rb +52 -0
  196. data/lib/carray/methods/mask_duplicates.rb +41 -0
  197. data/lib/carray/methods/meshgrid.rb +90 -0
  198. data/lib/carray/methods/mode.rb +126 -0
  199. data/lib/carray/methods/nunique.rb +46 -0
  200. data/lib/carray/methods/resize.rb +56 -0
  201. data/lib/carray/methods/snap.rb +161 -0
  202. data/lib/carray/methods/string_format.rb +57 -0
  203. data/lib/carray/methods/unique.rb +47 -0
  204. data/lib/carray/methods/value_counts.rb +71 -0
  205. data/lib/carray/mkmf.rb +124 -101
  206. data/lib/carray/runtime.rb +89 -0
  207. data/lib/carray/serialize.rb +478 -167
  208. data/lib/carray/slab_iterator.rb +305 -0
  209. data/lib/carray/stack.rb +291 -0
  210. data/lib/carray/string.rb +56 -180
  211. data/lib/carray/string_operation_extension.rb +289 -0
  212. data/lib/carray/struct.rb +335 -323
  213. data/lib/carray/struct_builder.rb +697 -0
  214. data/lib/carray/table.rb +41 -2
  215. data/lib/carray/time.rb +2654 -38
  216. data/lib/carray/window_iterator.rb +927 -0
  217. data/lib/carray.rb +55 -57
  218. data/yard-stubs/ca_obj_array.rb +385 -0
  219. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  220. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  221. data/yard-stubs/ca_obj_block.rb +73 -0
  222. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  223. data/yard-stubs/ca_obj_fake.rb +31 -0
  224. data/yard-stubs/ca_obj_farray.rb +32 -0
  225. data/yard-stubs/ca_obj_field.rb +45 -0
  226. data/yard-stubs/ca_obj_grid.rb +35 -0
  227. data/yard-stubs/ca_obj_refer.rb +72 -0
  228. data/yard-stubs/ca_obj_roll.rb +45 -0
  229. data/yard-stubs/ca_obj_shift.rb +43 -0
  230. data/yard-stubs/ca_obj_stride.rb +181 -0
  231. data/yard-stubs/ca_obj_tile.rb +29 -0
  232. data/yard-stubs/ca_obj_transpose.rb +40 -0
  233. data/yard-stubs/ca_obj_window.rb +49 -0
  234. data/yard-stubs/carray_access.rb +131 -0
  235. data/yard-stubs/carray_attribute.rb +246 -0
  236. data/yard-stubs/carray_broadcast.rb +37 -0
  237. data/yard-stubs/carray_cast.rb +489 -0
  238. data/yard-stubs/carray_class.rb +65 -0
  239. data/yard-stubs/carray_conversion.rb +76 -0
  240. data/yard-stubs/carray_copy.rb +79 -0
  241. data/yard-stubs/carray_core.rb +114 -0
  242. data/yard-stubs/carray_count.rb +79 -0
  243. data/yard-stubs/carray_element.rb +108 -0
  244. data/yard-stubs/carray_generate.rb +66 -0
  245. data/yard-stubs/carray_lazy.rb +23 -0
  246. data/yard-stubs/carray_loop.rb +140 -0
  247. data/yard-stubs/carray_mask.rb +259 -0
  248. data/yard-stubs/carray_math.rb +132 -0
  249. data/yard-stubs/carray_mathfunc.rb +45 -0
  250. data/yard-stubs/carray_median_percentile.rb +89 -0
  251. data/yard-stubs/carray_memory_view.rb +163 -0
  252. data/yard-stubs/carray_order.rb +312 -0
  253. data/yard-stubs/carray_random.rb +89 -0
  254. data/yard-stubs/carray_scatter.rb +106 -0
  255. data/yard-stubs/carray_slab.rb +57 -0
  256. data/yard-stubs/carray_sort.rb +163 -0
  257. data/yard-stubs/carray_test.rb +85 -0
  258. data/yard-stubs/carray_undef.rb +64 -0
  259. data/yard-stubs/carray_utils.rb +97 -0
  260. data/yard-stubs/ruby_carray.rb +193 -0
  261. metadata +220 -138
  262. data/Rakefile +0 -51
  263. data/TODO.md +0 -18
  264. data/ext/ca_iter_block.c +0 -257
  265. data/ext/ca_iter_dimension.c +0 -299
  266. data/ext/ca_iter_window.c +0 -214
  267. data/ext/ca_obj_mapping.c +0 -644
  268. data/ext/ca_obj_unbound_repeat.c +0 -529
  269. data/ext/carray_iterator.c +0 -641
  270. data/ext/carray_math.rb +0 -850
  271. data/ext/carray_numeric.c +0 -259
  272. data/ext/carray_sort_addr.c +0 -254
  273. data/ext/carray_stat.c +0 -2100
  274. data/ext/carray_stat_proc.rb +0 -1999
  275. data/ext/mkmath.rb +0 -741
  276. data/ext/ruby_ccomplex.c +0 -509
  277. data/ext/ruby_float_func.c +0 -86
  278. data/lib/carray/array.rb +0 -8
  279. data/lib/carray/autoload/autoload_base.rb +0 -19
  280. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  281. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  282. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  283. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  284. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  285. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  286. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  287. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  288. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  289. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  290. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  291. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  292. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  293. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  294. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  295. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  296. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  297. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  298. data/lib/carray/autoload.rb +0 -141
  299. data/lib/carray/basic.rb +0 -191
  300. data/lib/carray/broadcast.rb +0 -101
  301. data/lib/carray/compose.rb +0 -315
  302. data/lib/carray/convert.rb +0 -115
  303. data/lib/carray/info.rb +0 -110
  304. data/lib/carray/io/imagemagick.rb +0 -235
  305. data/lib/carray/mask.rb +0 -102
  306. data/lib/carray/math/histogram.rb +0 -177
  307. data/lib/carray/math/recurrence.rb +0 -93
  308. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  309. data/lib/carray/object/ca_obj_link.rb +0 -50
  310. data/lib/carray/object/ca_obj_pack.rb +0 -99
  311. data/lib/carray/obsolete.rb +0 -256
  312. data/lib/carray/ordering.rb +0 -181
  313. data/lib/carray/testing.rb +0 -51
  314. data/lib/carray/transform.rb +0 -109
  315. data/mailmap +0 -1
  316. data/misc/Methods.ja.md +0 -182
  317. data/misc/NOTE +0 -51
  318. data/spec/Classes/CABitfield_spec.rb +0 -58
  319. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  320. data/spec/Classes/CABlock_spec.rb +0 -205
  321. data/spec/Classes/CAField_spec.rb +0 -39
  322. data/spec/Classes/CAGrid_spec.rb +0 -75
  323. data/spec/Classes/CAMap_spec.rb +0 -0
  324. data/spec/Classes/CAMapping_spec.rb +0 -105
  325. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  326. data/spec/Classes/CAObject_spec.rb +0 -33
  327. data/spec/Classes/CARefer_spec.rb +0 -93
  328. data/spec/Classes/CARepeat_spec.rb +0 -65
  329. data/spec/Classes/CASelect_spec.rb +0 -22
  330. data/spec/Classes/CAShift_spec.rb +0 -16
  331. data/spec/Classes/CAStruct_spec.rb +0 -71
  332. data/spec/Classes/CATranspose_spec.rb +0 -60
  333. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  334. data/spec/Classes/CAWindow_spec.rb +0 -54
  335. data/spec/Classes/CAWrap_spec.rb +0 -8
  336. data/spec/Classes/CArray_spec.rb +0 -184
  337. data/spec/Classes/CScalar_spec.rb +0 -55
  338. data/spec/Features/feature_130_spec.rb +0 -19
  339. data/spec/Features/feature_attributes_spec.rb +0 -280
  340. data/spec/Features/feature_boolean_spec.rb +0 -98
  341. data/spec/Features/feature_broadcast.rb +0 -116
  342. data/spec/Features/feature_cast_function.rb +0 -19
  343. data/spec/Features/feature_cast_spec.rb +0 -33
  344. data/spec/Features/feature_class_spec.rb +0 -84
  345. data/spec/Features/feature_complex_spec.rb +0 -42
  346. data/spec/Features/feature_composite_spec.rb +0 -124
  347. data/spec/Features/feature_convert_spec.rb +0 -46
  348. data/spec/Features/feature_copy_spec.rb +0 -123
  349. data/spec/Features/feature_creation_spec.rb +0 -84
  350. data/spec/Features/feature_element_spec.rb +0 -144
  351. data/spec/Features/feature_extream_spec.rb +0 -54
  352. data/spec/Features/feature_generate_spec.rb +0 -74
  353. data/spec/Features/feature_index_spec.rb +0 -69
  354. data/spec/Features/feature_mask_spec.rb +0 -580
  355. data/spec/Features/feature_math_spec.rb +0 -97
  356. data/spec/Features/feature_order_spec.rb +0 -146
  357. data/spec/Features/feature_ref_store_spec.rb +0 -209
  358. data/spec/Features/feature_serialization_spec.rb +0 -125
  359. data/spec/Features/feature_stat_spec.rb +0 -397
  360. data/spec/Features/feature_virtual_spec.rb +0 -48
  361. data/spec/Features/method_eq_spec.rb +0 -81
  362. data/spec/Features/method_is_nan_spec.rb +0 -12
  363. data/spec/Features/method_map_spec.rb +0 -54
  364. data/spec/Features/method_max_with.rb +0 -20
  365. data/spec/Features/method_min_with.rb +0 -19
  366. data/spec/Features/method_ne_spec.rb +0 -18
  367. data/spec/Features/method_project_spec.rb +0 -188
  368. data/spec/Features/method_ref_spec.rb +0 -27
  369. data/spec/Features/method_round_spec.rb +0 -11
  370. data/spec/Features/method_s_linspace_spec.rb +0 -48
  371. data/spec/Features/method_s_span_spec.rb +0 -14
  372. data/spec/Features/method_seq_spec.rb +0 -47
  373. data/spec/Features/method_sort_with.rb +0 -43
  374. data/spec/Features/method_sorted_with.rb +0 -29
  375. data/spec/Features/method_span_spec.rb +0 -42
  376. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  377. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  378. data/spec/spec_all.rb +0 -10
  379. data/utils/ca_ase.rb +0 -21
  380. data/utils/ca_methods.rb +0 -15
  381. data/utils/cast_checker.rb +0 -30
  382. data/utils/convert_test.rb +0 -73
  383. data/utils/extract_yard.rb +0 -22
  384. data/utils/guess_shape.rb +0 -76
  385. data/utils/monkey_patch_methods.rb +0 -62
  386. data/utils/remove_resource_fork.sh +0 -5
@@ -0,0 +1,642 @@
1
+ # CAFrame — DataFrame built on CArray columns.
2
+ #
3
+ # Internal structure (memo §3): a Hash of named columns, an axis name for
4
+ # the row axis, and an optional index column. Every column agrees on its
5
+ # axis-0 length N; trailing shape is free per column (§3.2). The only
6
+ # substance the frame adds is names — the columns themselves are borrowed
7
+ # CArrays and are handed back raw so callers escape to CArray (§4.3).
8
+ #
9
+ # View semantics follow CArray (§3.6): +df["col"]+ is the stored column
10
+ # itself (an alias), row slices and filters return view-frames sharing
11
+ # storage, and +copy+ is the way to an independent frame.
12
+
13
+ class CAFrame
14
+ # Integer data_types that select rows positionally when used as a df[] key.
15
+ INTEGER_TYPES = [:int8, :int16, :int32, :int64,
16
+ :uint8, :uint16, :uint32, :uint64].freeze
17
+ private_constant :INTEGER_TYPES
18
+
19
+ # @!visibility private
20
+ DEFAULT_AXIS_NAME = "row"
21
+
22
+ # Build a frame from a Hash of +name => column+. Columns may be CArrays or
23
+ # anything that answers +to_ca+ (Ruby Array, lazy view). All columns must
24
+ # share axis-0 length N.
25
+ #
26
+ # The column hash may be passed either explicitly (+CAFrame.new(hash,
27
+ # axis_name: ...)+) or as bare inline pairs (+CAFrame.new("a" => x, "b" =>
28
+ # y)+). +:axis_name+ / +:index+ are control options; any remaining
29
+ # (string-keyed) options are treated as columns.
30
+ #
31
+ # Column names are normalized to Strings, so a Symbol key in an explicit
32
+ # column hash is stringified. The Symbol rejection below applies only to the
33
+ # keyword channel, which doubles as the +:axis_name+ / +:index+ control-option
34
+ # channel: a stray Symbol there is a mistyped control option, not a column.
35
+ def initialize(columns = {}, **opts)
36
+ axis_name = opts.delete(:axis_name)
37
+ index = opts.delete(:index)
38
+ stray = opts.keys.reject { |k| k.is_a?(String) }
39
+ unless stray.empty?
40
+ raise ArgumentError,
41
+ "column keys must be Strings (Symbols are reserved); got #{stray.inspect}"
42
+ end
43
+ columns = columns.merge(opts) unless opts.empty?
44
+
45
+ @columns = {}
46
+ @axis_name = axis_name || DEFAULT_AXIS_NAME
47
+ @index = nil
48
+
49
+ n = nil
50
+ columns.each do |name, col|
51
+ key = name.to_s
52
+ ca = coerce_column(col)
53
+ len = ca.shape[0]
54
+ if n.nil?
55
+ n = len
56
+ elsif len != n
57
+ raise ArgumentError,
58
+ "column #{key.inspect} has axis-0 length #{len}, expected #{n}"
59
+ end
60
+ @columns[key] = ca
61
+ end
62
+ @nrow = n || 0
63
+
64
+ if index
65
+ idx = coerce_column(index)
66
+ unless idx.ndim == 1
67
+ raise ArgumentError, "index must be a 1-D column (got ndim #{idx.ndim})"
68
+ end
69
+ if n && idx.shape[0] != n
70
+ raise ArgumentError,
71
+ "index length #{idx.shape[0]} does not match nrow #{n}"
72
+ end
73
+ @index = idx
74
+ @nrow = idx.shape[0] if n.nil?
75
+ end
76
+
77
+ # With an index, the row axis is named after it; a column of the same name
78
+ # would shadow the index in +row+ and +reset_index+. Reject the collision at
79
+ # construction so those paths never silently drop one for the other.
80
+ if @index && @columns.key?(@axis_name)
81
+ raise ArgumentError,
82
+ "axis_name #{@axis_name.inspect} collides with a column of the same name"
83
+ end
84
+ end
85
+
86
+ # --- df[...] : key type decides the axis (memo §13.2) ------------------
87
+
88
+ # String -> a column (raw CArray, the escape unit)
89
+ # String x2+ -> Array<CArray> (the escape unit, plural)
90
+ # Integer -> a row (Ruby Hash)
91
+ # Range(int)-> positional row slice (view-frame)
92
+ # boolean CA-> row filter (view-frame)
93
+ # integer CA-> row gather (view-frame)
94
+ #
95
+ # String keys escape: df[...] hands back the raw column(s), never a frame.
96
+ # One name collapses to a bare CArray; several give an Array of them (the
97
+ # "single collapses, plural is an array" rule of ca[i] vs ca[i..j]), so
98
+ # +t, rh = df["temp", "rh"]+ destructures. A column-subset *frame* comes
99
+ # from +select+ (memo §13.2).
100
+ def [](*keys)
101
+ if keys.size > 1
102
+ unless keys.all? { |k| k.is_a?(String) }
103
+ raise ArgumentError,
104
+ "multi-key df[...] escapes columns; every key must be a String " \
105
+ "(use df.select(...) for a subset frame)"
106
+ end
107
+ return keys.map { |k| @columns.fetch(k) { raise KeyError, "no column #{k.inspect}" } }
108
+ end
109
+
110
+ key = keys.first
111
+ case key
112
+ when String
113
+ @columns.fetch(key) { raise KeyError, "no column #{key.inspect}" }
114
+ when Integer
115
+ row(key)
116
+ when Range
117
+ unless positional_range?(key)
118
+ raise ArgumentError,
119
+ "df[range] takes positional (integer) ranges only; " \
120
+ "use filter { |f| f.index ... } for label ranges"
121
+ end
122
+ select_rows(key)
123
+ when CArray
124
+ case key.data_type
125
+ when :boolean
126
+ select_rows(key)
127
+ when *INTEGER_TYPES
128
+ select_rows(key)
129
+ else
130
+ raise ArgumentError,
131
+ "CArray df[] key must be boolean or integer (got #{key.data_type})"
132
+ end
133
+ when Symbol
134
+ raise NotImplementedError, "df[symbol] is reserved for predicate keys"
135
+ else
136
+ raise ArgumentError, "unsupported df[] key: #{key.class}"
137
+ end
138
+ end
139
+
140
+ # --- df[...] = value : row-level assignment ---------------------------
141
+ #
142
+ # The RHS value decides the operation; the key selects the rows:
143
+ #
144
+ # df[sel] = UNDEF -> mask the selected rows across every column, in place.
145
+ # Shape is unchanged and the write goes through to the
146
+ # column storage; the index stays, so the rows remain
147
+ # identifiable.
148
+ # df[sel] = nil -> delete the selected rows. The frame shrinks and the
149
+ # survivors keep their order.
150
+ # df[sel] = other -> splice: replace the selected (contiguous) rows with
151
+ # +other+'s rows. +other+ may carry any number of rows,
152
+ # so the row count changes (Ruby Array#[]= splice
153
+ # semantics); its column set must match exactly.
154
+ #
155
+ # +sel+ is a row-axis indexer key, classified exactly as CArray classifies a
156
+ # 1-D column key (docs/topics/Indexer_decision_tree.md): a slice (BLOCK —
157
+ # Range / ArithmeticSequence / [start, count, step]), a boolean CArray
158
+ # (SELECT), an integer CArray (GRID), or an Integer (a single row). Mask and
159
+ # delete take any of them and are forwarded to the column indexer, so their
160
+ # errors match the rest of CArray. Splice reuses the same classifier
161
+ # (CArray.scan_index) but needs a contiguous slice, so it takes an Integer or
162
+ # a step-1 BLOCK (Range, step-1 ArithmeticSequence, or [start, count]); a
163
+ # strided or scattered selector raises.
164
+ #
165
+ # Delete and splice rebuild the column set, so an alias previously taken
166
+ # from this frame no longer tracks the frame's new column identity
167
+ # (@columns is rebound to a fresh Hash). Reads through the old alias see
168
+ # the ORIGINAL column's data (unchanged by splice construction), matching
169
+ # the pre-splice snapshot. Writes flow along the CAMeld chain:
170
+ #
171
+ # - Writes reaching the HEAD or TAIL segment (the parts of the original
172
+ # column that survived the splice) land back in that column's storage.
173
+ # External aliases into the original column see those writes — this is
174
+ # the chain composability that CAMeld deliberately preserves: a
175
+ # reference stays connected to what it references.
176
+ # - Writes reaching the spliced MIDDLE segment (the rows from +other+)
177
+ # do NOT reach +other+'s storage: +other+ is snapshotted via +.copy+
178
+ # at splice time so it behaves like a value that was handed over.
179
+ # +df1[...] = df2+ followed by writes to df1's spliced rows leaves
180
+ # df2 untouched, which matches the usual "assignment" intuition for
181
+ # the RHS of +[]=+.
182
+ #
183
+ # Callers who want write isolation on the head/tail segments too can
184
+ # materialise explicitly with +col_view.copy+ before the splice, or
185
+ # +df["c"] = df["c"].copy+ afterwards. The shape-preserving +UNDEF+
186
+ # write path always propagates to derived views (unchanged from
187
+ # previous behaviour).
188
+ def []=(*keys, value)
189
+ if keys.size != 1
190
+ raise ArgumentError,
191
+ "df[...] = takes one key (a column name or a row selector); " \
192
+ "got #{keys.size}"
193
+ end
194
+ selector = keys.first
195
+
196
+ # The key picks the axis, exactly as it does when reading (§13.2): a
197
+ # String names a column, anything else selects rows.
198
+ if selector.is_a?(String)
199
+ column_assign(selector, value)
200
+ elsif UNDEF.equal?(value)
201
+ mask_rows(selector)
202
+ elsif value.nil?
203
+ delete_rows(selector)
204
+ elsif value.is_a?(CAFrame)
205
+ splice_rows(selector, value)
206
+ else
207
+ raise ArgumentError,
208
+ "df[...] = expects UNDEF (mask rows), nil (delete rows), or a " \
209
+ "CAFrame (splice rows); got #{value.class}"
210
+ end
211
+ value
212
+ end
213
+
214
+ # df["name"] = ... : the column forms of []=. Unlike the verbs, +[]=+ can
215
+ # only ever mutate the receiver -- Ruby hands the right-hand side back as
216
+ # the value of an assignment, so there is no way to return a new frame.
217
+ # Membership is per-frame (§12-B), so a parent frame's column set is
218
+ # untouched.
219
+ #
220
+ # df["temp"] = col -> bind the name to that column (a new name is added)
221
+ # df["temp"] = nil -> remove the column
222
+ # df["temp"] = UNDEF -> mask every cell of the column, in place
223
+ private def column_assign(key, value)
224
+ if value.nil?
225
+ delete_column(key)
226
+ elsif UNDEF.equal?(value)
227
+ mask_column(key)
228
+ elsif value.is_a?(CAFrame)
229
+ raise ArgumentError,
230
+ "df[name] = takes a column, not a CAFrame; escape one with " \
231
+ "other[\"name\"], or splice rows with df[rows] = other"
232
+ else
233
+ rebind_column(key, value)
234
+ end
235
+ end
236
+
237
+ # Bind +key+ to +column+, adding the name when it is new. This is the one
238
+ # place a column enters an existing frame, so it is where the axis-0 length
239
+ # invariant is enforced (§12-A). It is a replacement rather than an edit,
240
+ # which is what sets it apart from the rest: +fill+ / +mask_eq+ /
241
+ # +df[rows] = UNDEF+ write to the shared column and are therefore visible
242
+ # wherever it is held, while this binds the name to a different column and
243
+ # leaves the old one alone (the same rule +cast+ follows, for the physical
244
+ # reason that a type change cannot reuse the buffer).
245
+ private def rebind_column(key, column)
246
+ if @index && key == @axis_name
247
+ raise ArgumentError,
248
+ "#{key.inspect} names the index, not a column; " \
249
+ "use set_index / reset_index to change the index"
250
+ end
251
+ ca = coerce_column(column)
252
+ len = ca.shape[0]
253
+ if @columns.empty? && @index.nil? && @nrow.zero?
254
+ @nrow = len # the first column of an empty frame fixes N
255
+ elsif len != @nrow
256
+ raise ArgumentError,
257
+ "column #{key.inspect} has axis-0 length #{len}, expected #{@nrow}"
258
+ end
259
+ @columns[key] = ca
260
+ end
261
+
262
+ # Remove a column. +drop+ is the same edit as a new frame (§3.8); this is
263
+ # the in-place form, which is all an assignment can be.
264
+ private def delete_column(key)
265
+ raise KeyError, "no column #{key.inspect}" unless @columns.key?(key)
266
+ @columns.delete(key)
267
+ @nrow = 0 if @columns.empty? && @index.nil?
268
+ self
269
+ end
270
+
271
+ # Mask every cell of a column, in place. The row form (+df[rows] = UNDEF+)
272
+ # masks the selected rows across every column; this is its column-wise
273
+ # counterpart, and like it the write goes through to the stored column and
274
+ # is visible through every alias.
275
+ private def mask_column(key)
276
+ col = @columns.fetch(key) { raise KeyError, "no column #{key.inspect}" }
277
+ col[] = UNDEF
278
+ self
279
+ end
280
+
281
+ # Column projection (memo §13.2): a view-frame holding the named columns as
282
+ # aliases (zero-copy, sharing storage with the parent frame). Distinct from
283
+ # +df[...]+, which escapes columns to raw CArrays, and from +filter+, which
284
+ # selects rows. +select+ takes no block — row conditions go through +filter+
285
+ # (old carray-dataframe fused the two into +select(cols) { cond }+; here they
286
+ # are orthogonal and compose by chaining: +df.select(...).filter { ... }+).
287
+ def select(*names)
288
+ raise ArgumentError, "select requires at least one column name" if names.empty?
289
+ cols = {}
290
+ names.each do |name|
291
+ key = name.to_s
292
+ raise KeyError, "no column #{key.inspect}" unless @columns.key?(key)
293
+ cols[key] = @columns[key]
294
+ end
295
+ rebuild(cols)
296
+ end
297
+
298
+ # Filter rows with a block that receives the frame and returns a boolean
299
+ # column (memo §15). The block builds the mask from +f["col"]+ / +f.index+.
300
+ # Keep the rows where the block's boolean selector is true.
301
+ #
302
+ # A masked (UNDEF) selector cell means the row's membership is *genuinely
303
+ # undetermined* (e.g. the predicate read a masked input). By default such
304
+ # rows are silently dropped, exactly as a false cell would be. Pass
305
+ # +keep_masked: true+ to carry the UNDEF forward instead: the undetermined
306
+ # rows survive into the result with their data cells masked (and their index
307
+ # value kept, so the row stays identifiable), leaving a later, better-informed
308
+ # pass to re-judge them. Definitely-true rows carry their values through
309
+ # unchanged in both modes.
310
+ def filter(keep_masked: false)
311
+ mask = yield(self)
312
+ unless mask.is_a?(CArray) && mask.data_type == :boolean
313
+ raise ArgumentError,
314
+ "filter block must return a boolean CArray (got #{mask.class})"
315
+ end
316
+ unless mask.shape[0] == @nrow
317
+ raise ArgumentError,
318
+ "filter mask has axis-0 length #{mask.shape[0]}, expected nrow #{@nrow}"
319
+ end
320
+ select_rows(mask, keep_masked: keep_masked)
321
+ end
322
+
323
+ # Move a column to the index and name the row axis after it (memo §3).
324
+ # An index-role change: the data is unchanged, so this mutates self and
325
+ # returns it (memo §3.8). Any existing index is replaced.
326
+ def set_index(name)
327
+ key = name.to_s
328
+ raise KeyError, "no column #{key.inspect}" unless @columns.key?(key)
329
+ idx = @columns[key]
330
+ unless idx.ndim == 1
331
+ raise ArgumentError, "index must be a 1-D column (got ndim #{idx.ndim})"
332
+ end
333
+ @columns.delete(key)
334
+ @index = idx
335
+ @axis_name = key
336
+ @nrow = idx.shape[0]
337
+ self
338
+ end
339
+
340
+ # Drop the index back into an ordinary column (the inverse of set_index).
341
+ # Also an index-role change, so it mutates self (memo §3.8). The former
342
+ # index becomes the first column, named after the row axis.
343
+ def reset_index
344
+ return self unless @index
345
+ @columns = { @axis_name => @index }.merge(@columns)
346
+ @index = nil
347
+ @axis_name = DEFAULT_AXIS_NAME
348
+ self
349
+ end
350
+
351
+ # The first +n+ rows as a positional view-frame (memo §11.3). +n+ larger
352
+ # than +nrow+ yields the whole frame; +n+ of 0 yields an empty frame.
353
+ def head(n = 5)
354
+ raise ArgumentError, "head count must be non-negative (got #{n})" if n < 0
355
+ row_span(0, [n, @nrow].min)
356
+ end
357
+
358
+ # The last +n+ rows as a positional view-frame (memo §11.3). +n+ larger
359
+ # than +nrow+ yields the whole frame; +n+ of 0 yields an empty frame.
360
+ def tail(n = 5)
361
+ raise ArgumentError, "tail count must be non-negative (got #{n})" if n < 0
362
+ row_span(@nrow - [n, @nrow].min, @nrow)
363
+ end
364
+
365
+ # The single row whose index label equals +label+, as a Ruby Hash (memo
366
+ # §13.2b). +label+ is matched exactly against the index (+index.eq+), so any
367
+ # orderable / object / datetime / categorical index works. The return type is
368
+ # a row Hash and stays that way: zero matches raise KeyError, and duplicate
369
+ # labels raise (go through +filter { |f| f.index.eq(label) }+ for the
370
+ # multi-row, frame-returning path). Positional access is +df[i]+.
371
+ def at(label)
372
+ raise ArgumentError, "at requires an index (set one with set_index)" unless @index
373
+ pos = @index.eq(label).where
374
+ case pos.elements
375
+ when 0
376
+ raise KeyError, "no row with index label #{label.inspect}"
377
+ when 1
378
+ row(pos[0])
379
+ else
380
+ raise ArgumentError,
381
+ "index label #{label.inspect} matches #{pos.elements} rows; " \
382
+ "use filter { |f| f.index.eq(label) } for duplicate labels"
383
+ end
384
+ end
385
+
386
+ # An independent frame — every column and the index materialized (§3.6).
387
+ def copy
388
+ cols = {}
389
+ @columns.each { |k, v| cols[k] = v.copy }
390
+ CAFrame.new(cols, axis_name: @axis_name, index: @index && @index.copy)
391
+ end
392
+
393
+ # +dup+ / +clone+ share the column data -- that is the CArray dup contract
394
+ # (§3.6) -- but must not share the columns Hash itself: Ruby's shallow copy
395
+ # hands over the same Hash object, so a membership edit (+append+ / +drop+ /
396
+ # a +cast+ rebind) on the copy would show up in the original, which is the
397
+ # one thing per-frame membership (§12-B) says never happens.
398
+ private def initialize_copy(other)
399
+ super
400
+ @columns = @columns.dup
401
+ end
402
+
403
+ # --- metadata readers (memo §13.2) ------------------------------------
404
+ # Each returns a fresh object; the live columns Hash is never exposed.
405
+
406
+ # Variable (column) names in column order. Returns +Array<String>+.
407
+ def variable_names
408
+ @columns.keys
409
+ end
410
+
411
+ # Variables (raw column CArrays) in column order. Returns
412
+ # +Array<CArray>+. Equivalent to +df[*variable_names]+ but built
413
+ # directly from the columns Hash.
414
+ def variables
415
+ @columns.values
416
+ end
417
+
418
+ # Number of variables (columns).
419
+ def nvar
420
+ @columns.size
421
+ end
422
+
423
+ # Number of rows (axis-0 length N).
424
+ attr_reader :nrow
425
+
426
+ # Row-axis name.
427
+ attr_reader :axis_name
428
+
429
+ # Index column (CArray) or nil.
430
+ attr_reader :index
431
+
432
+ # name => data_type, freshly derived.
433
+ def data_types
434
+ h = {}
435
+ @columns.each { |k, v| h[k] = v.data_type }
436
+ h
437
+ end
438
+
439
+ private def rebuild(cols)
440
+ CAFrame.new(cols, axis_name: @axis_name, index: @index)
441
+ end
442
+
443
+ private def coerce_column(col)
444
+ if col.is_a?(CAFrame)
445
+ raise ArgumentError,
446
+ "a CAFrame cannot be a column (it answers to_ca as a 2-D matrix); " \
447
+ "escape a column with df[\"name\"] or df.to_ca first"
448
+ end
449
+ unless col.respond_to?(:to_ca)
450
+ raise ArgumentError, "column must be a CArray or Array (got #{col.class})"
451
+ end
452
+ ca = col.to_ca
453
+ unless ca.is_a?(CArray)
454
+ raise ArgumentError, "column did not convert to a CArray (got #{ca.class})"
455
+ end
456
+ ca
457
+ end
458
+
459
+ private def row(i)
460
+ i += @nrow if i < 0
461
+ unless i >= 0 && i < @nrow
462
+ raise IndexError, "row index #{i} out of range (nrow=#{@nrow})"
463
+ end
464
+ h = {}
465
+ h[@axis_name] = elem_at(@index, i) if @index
466
+ @columns.each { |name, col| h[name] = elem_at(col, i) }
467
+ h
468
+ end
469
+
470
+ private def elem_at(col, i)
471
+ col[i, *([nil] * (col.ndim - 1))]
472
+ end
473
+
474
+ private def row_span(lo, hi)
475
+ if hi <= lo
476
+ select_rows(CArray.int32(0))
477
+ else
478
+ select_rows(lo...hi)
479
+ end
480
+ end
481
+
482
+ # df[sel] = UNDEF : mask the selected rows in place (write-through). Shape
483
+ # and index are untouched; the selected cells of every column go to UNDEF.
484
+ # The selector is forwarded to the column indexer, which classifies it.
485
+ private def mask_rows(selector)
486
+ @columns.each_value do |col|
487
+ col[selector, *([nil] * (col.ndim - 1))] = UNDEF
488
+ end
489
+ self
490
+ end
491
+
492
+ # df[sel] = nil : drop the selected rows. The surviving rows are gathered by
493
+ # the complement of the selection (order preserved), and the frame shrinks.
494
+ private def delete_rows(selector)
495
+ keep = selected_row_mask(selector).not
496
+ new_cols = {}
497
+ @columns.each do |name, col|
498
+ new_cols[name] = col[keep, *([nil] * (col.ndim - 1))]
499
+ end
500
+ @columns = new_cols
501
+ @index = @index[keep] if @index
502
+ @nrow = keep.count(true)
503
+ self
504
+ end
505
+
506
+ # df[sel] = other : replace the selected contiguous span with other's rows
507
+ # (any length). Columns are concatenated head + other + tail, so data types
508
+ # promote and the row count shifts by other.nrow - span.
509
+ private def splice_rows(selector, other)
510
+ lo, hi = contiguous_span(selector)
511
+ unless other.variable_names.sort == variable_names.sort
512
+ raise ArgumentError,
513
+ "splice frame has columns #{other.variable_names.inspect}, " \
514
+ "expected the same set as #{variable_names.inspect}"
515
+ end
516
+ new_cols = {}
517
+ @columns.each do |name, col|
518
+ tail = [nil] * (col.ndim - 1)
519
+ pieces = []
520
+ pieces << col[0...lo, *tail] if lo > 0
521
+ # Snapshot the RHS: df1[...] = df2 is a value assignment, so subsequent
522
+ # writes to df1's spliced rows must not leak into df2 via the CAMeld
523
+ # chain. Head / tail slices come from df1 itself and keep the intended
524
+ # chain composability (writes to df1["c"][k] still reach df1's own
525
+ # storage there — external aliases into df1's old column see them).
526
+ pieces << other[name].copy if other.nrow > 0
527
+ pieces << col[hi...@nrow, *tail] if hi < @nrow
528
+ new_cols[name] =
529
+ case pieces.size
530
+ when 0 then col[CArray.int32(0), *tail] # replaced every row with none
531
+ when 1 then pieces.first
532
+ else CArray.meld(pieces, axis: 0) # CAMeld view; data type mismatch
533
+ end # across pieces raises.
534
+ # For data type conversion, cast
535
+ # the incoming +other+'s
536
+ # column beforehand — silent
537
+ # promotion in a splice would
538
+ # hide schema drift.
539
+ end
540
+ new_index = splice_index(other, lo, hi)
541
+ @columns = new_cols
542
+ @index = new_index
543
+ @nrow = @nrow - (hi - lo) + other.nrow
544
+ self
545
+ end
546
+
547
+ # The index for a splice: head + other's index + tail. When this frame has
548
+ # an index, the spliced-in frame must have one too (its rows need labels).
549
+ private def splice_index(other, lo, hi)
550
+ return nil unless @index
551
+ if other.nrow > 0 && other.index.nil?
552
+ raise ArgumentError,
553
+ "splice frame needs an index because the target frame has one"
554
+ end
555
+ pieces = []
556
+ pieces << @index[0...lo] if lo > 0
557
+ pieces << other.index.copy if other.nrow > 0 # snapshot RHS (see splice_rows)
558
+ pieces << @index[hi...@nrow] if hi < @nrow
559
+ case pieces.size
560
+ when 0 then @index[CArray.int32(0)]
561
+ when 1 then pieces.first
562
+ else CArray.meld(pieces, axis: 0) # index data type must match across frames
563
+ end
564
+ end
565
+
566
+ # A boolean mask over the rows, true where +selector+ selects. Built by
567
+ # marking a false vector through the same selector the reader accepts, so
568
+ # Range / Integer / boolean / integer-array all normalize the same way.
569
+ private def selected_row_mask(selector)
570
+ m = CArray.boolean(@nrow) { 0 }
571
+ m[selector] = 1
572
+ m
573
+ end
574
+
575
+ # Resolve a contiguous span [lo, hi) for splice, reusing the row-axis indexer
576
+ # classifier (CArray.scan_index) so negatives, exclusive ends,
577
+ # ArithmeticSequence and the [start, count(, step)] array forms parse exactly
578
+ # as they do for a column key. Only a POINT (single row) or a step-1 BLOCK
579
+ # (contiguous slice) has a span to replace; a strided or scattered selector
580
+ # raises.
581
+ #
582
+ # The one place splice diverges from element indexing is the tail insertion
583
+ # point: scan_index bound-checks the start to 0..nrow-1, but splice — like
584
+ # Ruby Array#[]= — also accepts nrow as an empty span to append to, written
585
+ # +nrow...nrow+.
586
+ private def contiguous_span(selector)
587
+ if (selector.is_a?(Range) || selector.is_a?(Enumerator::ArithmeticSequence)) &&
588
+ selector.exclude_end? && selector.begin == @nrow && selector.end == @nrow
589
+ return [@nrow, @nrow]
590
+ end
591
+
592
+ info = CArray.scan_index([@nrow], [selector])
593
+ case info.type
594
+ when CA_REG_POINT
595
+ i = info.index[0]
596
+ [i, i + 1]
597
+ when CA_REG_BLOCK
598
+ start, count, step = info.index[0]
599
+ unless step == 1
600
+ raise ArgumentError,
601
+ "splice needs a contiguous slice; a strided step #{step} has no span"
602
+ end
603
+ [start, start + count]
604
+ else
605
+ raise ArgumentError,
606
+ "splice (df[...] = frame) needs a contiguous slice (Range, Integer, " \
607
+ "or [start, count]); got #{selector.class}"
608
+ end
609
+ end
610
+
611
+ private def select_rows(selector, keep_masked: false)
612
+ if keep_masked && selector.is_a?(CArray) &&
613
+ selector.data_type == :boolean && selector.has_mask?
614
+ return select_rows_keep_masked(selector)
615
+ end
616
+ cols = {}
617
+ @columns.each do |name, col|
618
+ cols[name] = col[selector, *([nil] * (col.ndim - 1))]
619
+ end
620
+ new_index = @index ? @index[selector] : nil
621
+ CAFrame.new(cols, axis_name: @axis_name, index: new_index)
622
+ end
623
+
624
+ private def select_rows_keep_masked(selector)
625
+ keep = selector.strip_mask(1) # true where selected OR undetermined
626
+ undet_kept = selector.is_masked[keep] # among kept rows, which were undetermined
627
+ cols = {}
628
+ @columns.each do |name, col|
629
+ tail = [nil] * (col.ndim - 1)
630
+ g = col[keep, *tail].copy
631
+ g[undet_kept, *tail] = UNDEF
632
+ cols[name] = g
633
+ end
634
+ new_index = @index ? @index[keep] : nil
635
+ CAFrame.new(cols, axis_name: @axis_name, index: new_index)
636
+ end
637
+
638
+ private def positional_range?(range)
639
+ (range.begin.nil? || range.begin.is_a?(Integer)) &&
640
+ (range.end.nil? || range.end.is_a?(Integer))
641
+ end
642
+ end