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,248 @@
1
+ # CAFrame join (memo §5, §11.7, §13.3).
2
+ #
3
+ # Join is a thin delegation to landed addressing primitives: the join key
4
+ # yields an address array (locate_addr for a row-preserving left lookup,
5
+ # align_addr for symmetric set alignment), and each column is gathered by
6
+ # +project+ (length-preserving, miss -> UNDEF, Face-lift, read-only safe).
7
+ # The frame layer just distributes that address to every column.
8
+
9
+ class CAFrame
10
+ # Default disambiguation for non-key columns present on both sides (§12-C):
11
+ # left gets "_left", right gets "_right". +_left+/+_right+ read clearer than
12
+ # pandas' +_x+/+_y+ for the target audience.
13
+ DEFAULT_JOIN_SUFFIXES = ["_left", "_right"].freeze
14
+ private_constant :DEFAULT_JOIN_SUFFIXES
15
+
16
+ # Join +other+ on a shared key column.
17
+ #
18
+ # how: :left (default) — keep all left rows; right columns gathered per
19
+ # left row (locate_addr), misses become UNDEF.
20
+ # how: :inner/:outer/:right — set-align both key sets (align_addr) and
21
+ # gather both sides; the aligned key values form
22
+ # the +on+ column.
23
+ #
24
+ # A non-key column present on both sides collides. The key column (+on+) is
25
+ # kept once, never suffixed. By default the collision is resolved by suffixing
26
+ # both sides (+suffixes: ["_left", "_right"]+); pass a 2-element array to pick
27
+ # meaningful names up front (+["_obs", "_fcst"]+), or +suffixes: false+ to
28
+ # raise instead. Rename afterward with +rename+ if needed (§12-C).
29
+ def join(other, on:, how: :left, suffixes: DEFAULT_JOIN_SUFFIXES)
30
+ on = on.to_s
31
+ lkey = self[on]
32
+ rkey = other[on]
33
+ plan = join_name_plan(other, on, suffixes)
34
+
35
+ case how
36
+ when :left
37
+ join_left(other, on, lkey, rkey, plan)
38
+ when :inner, :outer, :right
39
+ join_align(other, on, lkey, rkey, how, plan)
40
+ else
41
+ raise ArgumentError, "unknown join mode #{how.inspect}"
42
+ end
43
+ end
44
+
45
+ # As-of (nearest-key) left join for irregular series (memo §11.7). Each
46
+ # left row is matched to the nearest +other+ row by +on+ via
47
+ # locate_nearest_addr (the per-row, row-preserving counterpart of
48
+ # align_nearest_addr); +direction:+ follows CArray (:floor = most recent
49
+ # at-or-before, :ceil = next, :round = nearest), and rows with no match
50
+ # in range or beyond +tolerance:+ come back UNDEF. Same wiring as the left
51
+ # join, only the address primitive differs.
52
+ def join_asof(other, on:, direction: :floor, tolerance: nil, suffixes: DEFAULT_JOIN_SUFFIXES)
53
+ on = on.to_s
54
+ plan = join_name_plan(other, on, suffixes)
55
+ addr = self[on].locate_nearest_addr(other[on], direction: direction, tolerance: tolerance)
56
+ join_by_addr(other, on, addr, plan)
57
+ end
58
+
59
+ # Conform every variable to an externally supplied reference key set (the
60
+ # asymmetric sibling of +join+; pandas +reindex+). Given a +reference+ array
61
+ # of key values, each column is gathered onto it by exact key match
62
+ # (+locate_addr+): the aligned key column (or index) becomes the reference
63
+ # itself, and a reference key absent from the source comes back UNDEF in
64
+ # every other column.
65
+ #
66
+ # CAFrame measures no interval and generates nothing -- the caller owns the
67
+ # reference. This is the primitive for reindexing to a caller-built axis
68
+ # (e.g. a complete 10-minute time grid): supply that axis as +reference+ and
69
+ # the gaps fill with UNDEF rows carrying only the reference key.
70
+ #
71
+ # df.align("time", reftime) # reftime = a CArray of reference key values
72
+ #
73
+ # +key+ may be a column name or the index axis name. +reference+ is a CArray
74
+ # (or Array); its keys are matched against the source key by value, so their
75
+ # data types must be comparable (a DateTime object key matches by eql?/hash).
76
+ def align(key, reference)
77
+ key = key.to_s
78
+ ref = reference.is_a?(CArray) ? reference : reference.to_ca
79
+ on_index = !@columns.key?(key) && @axis_name == key && @index
80
+ addr = ref.locate_addr(on_index ? @index : self[key])
81
+
82
+ cols = {}
83
+ variable_names.each { |name| cols[name] = project_rows(self[name], addr) }
84
+ if on_index
85
+ CAFrame.new(cols, axis_name: key, index: ref)
86
+ else
87
+ cols[key] = ref
88
+ CAFrame.new(cols, axis_name: @axis_name,
89
+ index: @index && project_rows(@index, addr))
90
+ end
91
+ end
92
+
93
+ # Paste +other+'s variables beside this frame's, matched by **row position**
94
+ # (memo §12-C) — a keyless column merge (the column-direction counterpart of
95
+ # the row-stacking +concat+, and the positional counterpart of the key-aligned
96
+ # +join+; named after the UNIX +paste+). Both frames must have the same
97
+ # +nrow+; rows are assumed to already correspond (no key alignment, consistent
98
+ # with the no-implicit-align stance). Colliding column names are disambiguated
99
+ # by the same policy as +join+ (default suffix +_left+/+_right+, +suffixes:+ to
100
+ # override, +suffixes: false+ to raise). This frame's index is kept; +other+'s
101
+ # index, if any, is not carried (only its columns are pasted). Returns a new
102
+ # frame.
103
+ #
104
+ # obs.paste(fcst) # side by side, same rows
105
+ # obs.paste(fcst, suffixes: ["_obs", "_fcst"])
106
+ def paste(other, suffixes: DEFAULT_JOIN_SUFFIXES)
107
+ CAFrame.paste(self, other, suffixes: suffixes)
108
+ end
109
+
110
+ # Paste N frames side by side by row position (symmetric N-ary sibling of
111
+ # the instance +#paste+; see the instance verb's docstring for the pair
112
+ # semantics). All frames must have the same +nrow+. The result's
113
+ # +axis_name+ and +index+ come from the first frame (peers, but a base is
114
+ # needed for the index). Column names that appear in more than one frame
115
+ # are colliding — pass +suffixes:+ as an Array of exactly K strings (one
116
+ # per input frame) to disambiguate them; otherwise the collision raises.
117
+ #
118
+ # CAFrame.paste(obs, fcst)
119
+ # CAFrame.paste(obs, ecmwf, gfs, suffixes: ["_obs", "_ecmwf", "_gfs"])
120
+ # CAFrame.paste([obs, fcst]) # Array is accepted too
121
+ def self.paste(*frames, suffixes: DEFAULT_JOIN_SUFFIXES)
122
+ frames = frames.flatten
123
+ raise ArgumentError, "paste requires at least one frame" if frames.empty?
124
+ unless frames.all? { |f| f.is_a?(CAFrame) }
125
+ raise ArgumentError, "paste expects CAFrame arguments"
126
+ end
127
+ first = frames.first
128
+ nrow = first.nrow
129
+ frames.each_with_index do |f, i|
130
+ next if i.zero?
131
+ unless f.nrow == nrow
132
+ raise ArgumentError,
133
+ "paste: row-count mismatch (frame #{i}: #{f.nrow} vs #{nrow}); " \
134
+ "use join for key alignment"
135
+ end
136
+ end
137
+
138
+ # Detect columns that appear in 2+ frames (collisions across the K inputs).
139
+ name_count = Hash.new(0)
140
+ frames.each { |f| f.variable_names.each { |name| name_count[name] += 1 } }
141
+ collisions = name_count.select { |_, n| n > 1 }.keys
142
+
143
+ if collisions.any?
144
+ if suffixes == false
145
+ raise ArgumentError,
146
+ "paste column name collision: #{collisions.inspect} " \
147
+ "(pass suffixes: [\"_a\", \"_b\", ...] with one entry per frame, " \
148
+ "or rename first)"
149
+ end
150
+ # For the 2-frame case DEFAULT_JOIN_SUFFIXES ("_left", "_right") works
151
+ # as-is; for K > 2 the caller must pass a K-length Array (or use the
152
+ # pair-only default indirectly via the instance method).
153
+ unless suffixes.is_a?(Array) && suffixes.size == frames.size &&
154
+ suffixes.all? { |s| s.is_a?(String) }
155
+ raise ArgumentError,
156
+ "paste: suffixes must be an Array of #{frames.size} Strings " \
157
+ "(one per frame), got #{suffixes.inspect}"
158
+ end
159
+ end
160
+
161
+ cols = {}
162
+ frames.each_with_index do |f, i|
163
+ f.variable_names.each do |name|
164
+ key = collisions.include?(name) ? "#{name}#{suffixes[i]}" : name
165
+ if cols.key?(key)
166
+ raise ArgumentError,
167
+ "paste: column name #{key.inspect} produced twice " \
168
+ "(collision after suffix from frame #{i})"
169
+ end
170
+ cols[key] = f[name]
171
+ end
172
+ end
173
+ new(cols, axis_name: first.axis_name, index: first.index)
174
+ end
175
+
176
+ private def join_name_plan(other, on, suffixes)
177
+ collisions = (variable_names & other.variable_names) - [on]
178
+ return { collisions: [], lsuf: nil, rsuf: nil } if collisions.empty?
179
+
180
+ if suffixes == false
181
+ raise ArgumentError,
182
+ "join column name collision: #{collisions.inspect} " \
183
+ "(pass suffixes: [\"_l\", \"_r\"] to disambiguate, or rename first)"
184
+ end
185
+ unless suffixes.is_a?(Array) && suffixes.size == 2 && suffixes.all? { |s| s.is_a?(String) }
186
+ raise ArgumentError,
187
+ "suffixes must be false or a 2-element array of Strings, got #{suffixes.inspect}"
188
+ end
189
+ { collisions: collisions, lsuf: suffixes[0], rsuf: suffixes[1] }
190
+ end
191
+
192
+ private def join_name(name, suffix, plan)
193
+ plan[:collisions].include?(name) ? "#{name}#{suffix}" : name
194
+ end
195
+
196
+ private def put_join_column(cols, name, col)
197
+ if cols.key?(name)
198
+ raise ArgumentError,
199
+ "join produces a duplicate column #{name.inspect} after suffixing; " \
200
+ "rename a column first or pass different suffixes"
201
+ end
202
+ cols[name] = col
203
+ end
204
+
205
+ private def join_left(other, on, lkey, rkey, plan)
206
+ join_by_addr(other, on, lkey.locate_addr(rkey), plan)
207
+ end
208
+
209
+ private def join_by_addr(other, on, addr, plan)
210
+ cols = {}
211
+ variable_names.each { |name| put_join_column(cols, join_name(name, plan[:lsuf], plan), self[name]) }
212
+ other.variable_names.each do |name|
213
+ next if name == on
214
+ put_join_column(cols, join_name(name, plan[:rsuf], plan), project_rows(other[name], addr))
215
+ end
216
+ CAFrame.new(cols, axis_name: @axis_name, index: @index)
217
+ end
218
+
219
+ private def join_align(other, on, lkey, rkey, how, plan)
220
+ common, a_idx, b_idx = CArray.align_addr(lkey, rkey, join: how)
221
+ cols = {}
222
+ variable_names.each do |name|
223
+ if name == on
224
+ put_join_column(cols, on, common)
225
+ else
226
+ put_join_column(cols, join_name(name, plan[:lsuf], plan), project_rows(self[name], a_idx))
227
+ end
228
+ end
229
+ other.variable_names.each do |name|
230
+ next if name == on
231
+ put_join_column(cols, join_name(name, plan[:rsuf], plan), project_rows(other[name], b_idx))
232
+ end
233
+ # Carry the left index the same way its columns are gathered (by a_idx),
234
+ # so it stays consistent with the left join; rows with no left match
235
+ # (outer/right) become UNDEF.
236
+ new_index = @index && project_rows(@index, a_idx)
237
+ CAFrame.new(cols, axis_name: @axis_name, index: new_index)
238
+ end
239
+
240
+ private def project_rows(col, addr)
241
+ return col.project(addr) if col.ndim == 1
242
+ trailing = col.shape[1..]
243
+ t = trailing.inject(1, :*)
244
+ n = addr.shape[0]
245
+ flat = addr.reshape(n, 1) * t + CArray.int64(t).seq.reshape(1, t)
246
+ col.project(flat).reshape(n, *trailing)
247
+ end
248
+ end
@@ -0,0 +1,99 @@
1
+ # CAFrame record (row-oriented) input (memo §11.2, §11.5).
2
+ #
3
+ # from_records takes an Array of row Hashes -- the shape JSON.parse yields for
4
+ # a JSON array of objects -- and arranges it into columns. Unlike from_csv,
5
+ # the cell values are already typed Ruby objects (Float / Integer / DateTime /
6
+ # String), so a homogeneous column is built at its native leaf type rather than
7
+ # left as strings ("arrange by the value's own type", not string inference --
8
+ # §4.2 stays intact: no date-like string is parsed, mixed columns stay object).
9
+ #
10
+ # An Array-valued cell that is the same length across every record becomes an
11
+ # N-D column (§11.5): { "temp" => [min, mean, max] } over N records is one
12
+ # (N, 3) column. Ragged or non-numeric arrays fall back to an object column.
13
+ #
14
+ # Missing keys and explicit nils become UNDEF for numeric columns (mask, not a
15
+ # float promotion): an int column with a hole stays int + UNDEF.
16
+
17
+ class CAFrame
18
+ # Build a frame from an Array of row Hashes. Column set is the union of keys
19
+ # in first-appearance order; keys are stringified. +types:+ casts named
20
+ # columns afterward (same map / array-key forms as +cast+).
21
+ def self.from_records(records, types: nil)
22
+ unless records.is_a?(Array) && records.all? { |r| r.is_a?(Hash) }
23
+ raise ArgumentError, "from_records expects an Array of Hashes"
24
+ end
25
+ return new if records.empty?
26
+
27
+ keys = record_key_union(records)
28
+ n = records.size
29
+ cols = {}
30
+ keys.each do |key|
31
+ values = records.map { |r| r[key] }
32
+ cols[key.to_s] = build_record_column(values, n)
33
+ end
34
+
35
+ frame = new(cols)
36
+ frame.cast(types) if types
37
+ frame
38
+ end
39
+
40
+ # Union of record keys in first-appearance order (original key objects, so a
41
+ # String- or Symbol-keyed set both work; the column name stringifies later).
42
+ def self.record_key_union(records)
43
+ seen = {}
44
+ keys = []
45
+ records.each do |r|
46
+ r.each_key do |k|
47
+ unless seen.key?(k)
48
+ seen[k] = true
49
+ keys << k
50
+ end
51
+ end
52
+ end
53
+ keys
54
+ end
55
+ private_class_method :record_key_union
56
+
57
+ # Arrange one column's values (Ruby objects, nil for missing) into a CArray.
58
+ # All-Array cells of equal length -> N-D native column; numeric scalars ->
59
+ # native scalar column (nil -> UNDEF); anything else -> object column.
60
+ def self.build_record_column(values, n)
61
+ present = values.reject(&:nil?)
62
+ return CArray.object(n) { values } if present.empty?
63
+
64
+ if present.all? { |v| v.is_a?(Array) || v.is_a?(CArray) }
65
+ build_nd_column(values, present, n)
66
+ else
67
+ type = numeric_leaf_type(present)
68
+ col = CArray.object(n) { values }
69
+ type ? col.to_type(type) : col
70
+ end
71
+ end
72
+ private_class_method :build_record_column
73
+
74
+ # Stack equal-length array cells into an (N, L) column via an object 2-D fill
75
+ # + to_type (nil rows -> UNDEF, int/float by leaf). Ragged lengths or
76
+ # non-numeric leaves fall back to a 1-D object column of the raw cells.
77
+ def self.build_nd_column(values, present, n)
78
+ lengths = present.map { |v| v.is_a?(CArray) ? v.shape[0] : v.size }
79
+ len = lengths.first
80
+ return CArray.object(n) { values } unless lengths.all? { |x| x == len }
81
+
82
+ nested = values.map { |v| v.nil? ? Array.new(len) : (v.is_a?(CArray) ? v.to_a : v) }
83
+ type = numeric_leaf_type(nested.flatten.compact)
84
+ table = CArray.object(n, len) { nested }
85
+ type ? table.to_type(type) : table
86
+ end
87
+ private_class_method :build_nd_column
88
+
89
+ # :int64 if every value is an Integer, :float64 if all are Numeric (int/float
90
+ # mix), otherwise nil (strings / DateTime / booleans / mixed -> keep object).
91
+ def self.numeric_leaf_type(values)
92
+ if values.all? { |v| v.is_a?(Integer) }
93
+ :int64
94
+ elsif values.all? { |v| v.is_a?(Numeric) }
95
+ :float64
96
+ end
97
+ end
98
+ private_class_method :numeric_leaf_type
99
+ end
@@ -0,0 +1,113 @@
1
+ # CAFrame row sort (memo §5, spine = addressing + gather).
2
+ #
3
+ # Two entry points, both delegating to the multi-key CArray.sort_addr -- the
4
+ # class-method lexicographic sort that returns flat row addresses (distinct from
5
+ # the single-key instance CArray#sort_index) -- and gathering every column and
6
+ # the index by that address into a view-frame in key order:
7
+ #
8
+ # sort_by_key(*keys, order:, masked_position:) -- sort by named columns, with
9
+ # per-key or blanket ascending/descending.
10
+ # sort_by { |f| ... } -- sort by key CArrays the block builds (the escape,
11
+ # §4.3): derived or composite keys are ordinary column
12
+ # math.
13
+ #
14
+ # sort_addr is ascending, so a descending key is expressed by replacing it with
15
+ # its dense descending rank (CArray#order(descending: true, method: :dense)):
16
+ # this works for every data type (unlike negation, which cannot reverse a string and
17
+ # silently wraps an unsigned integer), and the *dense* rank keeps equal values
18
+ # on one rank so ties fall through to later keys in a multi-key sort.
19
+ # masked_position (CArray.sort_addr's kwarg) places masked key rows first or
20
+ # last (default last).
21
+
22
+ class CAFrame
23
+ # Sort rows by one or more key columns, lexicographic (the first key is
24
+ # primary), returning a view-frame. Delegates to +CArray.sort_addr+ for the
25
+ # row permutation and gathers every column and the index by it.
26
+ #
27
+ # Each key is a **column name** (or the index axis name), or a
28
+ # **`[name, :asc | :desc]`** pair for a per-key direction. The +order:+ keyword
29
+ # is the direction for bare-name keys (default +:asc+). +masked_position:+
30
+ # sends masked key rows to the +:last+ (default) or +:first+ end.
31
+ #
32
+ # df.sort_by_key("temp") # one column, ascending
33
+ # df.sort_by_key("station", "temp") # lexicographic
34
+ # df.sort_by_key("temp", order: :desc) # all keys descending
35
+ # df.sort_by_key(["station", :asc], ["temp", :desc]) # per-key direction
36
+ # df.sort_by_key("temp", masked_position: :first) # masked rows first
37
+ # df.sort_by_key("time") # by the index
38
+ def sort_by_key(*specs, order: :asc, masked_position: :last)
39
+ raise ArgumentError, "sort_by_key requires at least one key" if specs.empty?
40
+ validate_sort_order(order)
41
+ keys = specs.map do |spec|
42
+ name, dir = parse_sort_key_spec(spec, order)
43
+ col = sort_key_column(name)
44
+ dir == :desc ? col.order(descending: true, method: :dense) : col
45
+ end
46
+ select_rows(CArray.sort_addr(*keys, masked_position: masked_position))
47
+ end
48
+
49
+ # Sort rows by key columns the block builds (memo §4.3 escape). The block
50
+ # receives the frame and returns a key CArray, or an Array of them for a
51
+ # multi-key lexicographic sort; rows are sorted ascending by +CArray.sort_addr+
52
+ # over those keys and gathered into a view-frame. This is the sort sibling of
53
+ # +filter+: where +sort_by_key+ takes plain column names, the block form lets
54
+ # you build any key with CArray ops -- a descending key is its dense
55
+ # descending rank, derived or composite keys are ordinary column math.
56
+ # +masked_position:+ places masked key rows first or last (default last).
57
+ #
58
+ # df.sort_by { |f| (f["temp"] - target).abs } # nearest-to-target first
59
+ # df.sort_by { |f| f["temp"].order(descending: true, method: :dense) }
60
+ def sort_by(masked_position: :last)
61
+ raise ArgumentError, "sort_by requires a block" unless block_given?
62
+ keys = yield(self)
63
+ keys = [keys] unless keys.is_a?(Array)
64
+ raise ArgumentError, "sort_by block must return a key CArray or an Array of them" if keys.empty?
65
+ keys.each_with_index do |k, i|
66
+ unless k.is_a?(CArray)
67
+ raise ArgumentError, "sort_by key #{i} must be a CArray (got #{k.class})"
68
+ end
69
+ unless k.ndim == 1 && k.shape[0] == nrow
70
+ raise ArgumentError,
71
+ "sort_by key #{i} must be a 1-D column of length #{nrow} (got shape #{k.shape.inspect})"
72
+ end
73
+ end
74
+ select_rows(CArray.sort_addr(*keys, masked_position: masked_position))
75
+ end
76
+
77
+ private def parse_sort_key_spec(spec, default_order)
78
+ case spec
79
+ when String, Symbol
80
+ [spec, default_order]
81
+ when Array
82
+ unless spec.size == 2 && (spec[0].is_a?(String) || spec[0].is_a?(Symbol))
83
+ raise ArgumentError, "sort key must be a name or [name, :asc|:desc], got #{spec.inspect}"
84
+ end
85
+ validate_sort_order(spec[1])
86
+ spec
87
+ else
88
+ raise ArgumentError, "sort key must be a name or [name, :asc|:desc], got #{spec.class}"
89
+ end
90
+ end
91
+
92
+ private def validate_sort_order(dir)
93
+ return if dir == :asc || dir == :desc
94
+ raise ArgumentError, "sort order must be :asc or :desc, got #{dir.inspect}"
95
+ end
96
+
97
+ private def sort_key_column(name)
98
+ key = name.to_s
99
+ col = if @columns.key?(key)
100
+ @columns[key]
101
+ elsif @index && @axis_name == key
102
+ @index
103
+ else
104
+ raise KeyError, "no column #{key.inspect}"
105
+ end
106
+ unless col.ndim == 1
107
+ raise ArgumentError,
108
+ "sort_by_key: key column #{key.inspect} is #{col.ndim}-D; " \
109
+ "sort by a scalar (1-D) column"
110
+ end
111
+ col
112
+ end
113
+ end