carray 2.0.0 → 3.0.0

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