carray 3.0.2 → 3.0.4

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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
@@ -41,6 +41,8 @@
41
41
 
42
42
  #include "ruby.h"
43
43
  #include "carray.h"
44
+ #include "carray_internal.h" /* ca_integer_arg, ca_calling_method_name */
45
+ #include "ca_obj_face.h" /* ca_face_refuse_numeric */
44
46
  #include <math.h>
45
47
 
46
48
  /* Externals (link-time; kept out of carray.h to keep the header lean). */
@@ -50,6 +52,7 @@ extern VALUE rb_ca_insert_axis (int argc, VALUE *argv, VALUE self); /* c
50
52
  static ID id_axis, id_sort_copy, id_is_not_masked;
51
53
  /* object lane (arbitrary Ruby objects -> arithmetic is irreducibly funcall) */
52
54
  static ID id_plus, id_div, id_mul, id_min, id_max, id_sort, id_copy;
55
+ static ID id_rationalize;
53
56
  static ID id_aref, id_aset;
54
57
  static VALUE sym_linear, sym_lower, sym_higher, sym_nearest, sym_midpoint;
55
58
 
@@ -71,13 +74,14 @@ mp_is_numeric (int8_t dt)
71
74
  _(CA_INT64, int64_t) _(CA_UINT64, uint64_t) \
72
75
  _(CA_FLOAT32, float32_t) _(CA_FLOAT64, double)
73
76
 
74
- /* [numeric] min over the upper region [k+1, n-1] of a partitioned fiber,
75
- skipping NaN (= matches production .min(axis:): NaN sorts to the tail
76
- and is ignored). stride = element stride along the reduce axis.
77
- fmin ignores NaN (fmin(x,NaN)==x), so a +INFINITY seed naturally skips
78
- NaN and stays branch-free -> the compiler vectorises this loop, matching
79
- the production SIMD min(axis:). An all-NaN (or empty) upper region
80
- leaves hi == +INFINITY; fall back to the kth value (degenerate). */
77
+ /* [numeric] sorted[k+1] of a fiber partitioned at k = the min over the
78
+ upper region [k+1, n-1]. NaN sorts after every number, so it is the
79
+ upper element only when the region holds nothing else. stride = element
80
+ stride along the reduce axis. fmin ignores NaN (fmin(x,NaN)==x), so a
81
+ +INFINITY seed skips NaN and the loop stays branch-free and vectorised.
82
+ A result of +INFINITY is either a +INFINITY in the region or a region of
83
+ NaN only; the rescan that tells them apart runs only in that case. An
84
+ empty region (k == n-1) answers the kth value. */
81
85
  #define MP_GEN_MINUP(CT, T) \
82
86
  static double \
83
87
  mp_minup_##T (const T *b, ca_size_t f0, long k, long n, long stride) { \
@@ -86,8 +90,11 @@ mp_is_numeric (int8_t dt)
86
90
  double v = (double) b[f0 + (ca_size_t)(ai * stride)]; \
87
91
  hi = fmin(hi, v); \
88
92
  } \
89
- return ( hi == (double) INFINITY ) \
90
- ? (double) b[f0 + (ca_size_t)(k * stride)] : hi; \
93
+ if ( hi != (double) INFINITY ) return hi; \
94
+ if ( k + 1 >= n ) return (double) b[f0 + (ca_size_t)(k * stride)]; \
95
+ for ( long ai = k + 1; ai < n; ai++ ) \
96
+ if ( (double) b[f0 + (ca_size_t)(ai * stride)] == hi ) return hi; \
97
+ return (double) NAN; \
91
98
  }
92
99
  MP_TYPES(MP_GEN_MINUP)
93
100
 
@@ -137,7 +144,12 @@ static VALUE
137
144
  mp_axis_all_masked (VALUE self, long axis, int keep_axis)
138
145
  {
139
146
  int8_t ax = (int8_t) axis;
140
- VALUE out = rb_ca_new_reduced(self, &ax, 1, CA_FLOAT64, keep_axis);
147
+ CArray *ca;
148
+ GetCArray(self, ca);
149
+ VALUE out = rb_ca_new_reduced(self, &ax, 1,
150
+ (ca->data_type == CA_OBJECT) ? CA_OBJECT
151
+ : CA_FLOAT64,
152
+ keep_axis);
141
153
  CArray *co;
142
154
  GetCArray(out, co);
143
155
  ca_create_mask(co);
@@ -185,8 +197,7 @@ pct_compute (VALUE method, long k, double r, long n, double lo, double hi)
185
197
  return use_k ? lo : hi;
186
198
  }
187
199
  if ( method == sym_linear ) {
188
- if ( r == 0.0 || k + 1 >= n ) return lo;
189
- return lo * (1.0 - r) + hi * r;
200
+ return ca_percentile_linear(lo, hi, r, k, n);
190
201
  }
191
202
  if ( method == sym_midpoint ) {
192
203
  if ( k + 1 >= n ) return lo;
@@ -240,7 +251,7 @@ median_fill (double *op, int8_t dt, const char *base,
240
251
  MP_TYPES(MP_CASE)
241
252
  #undef MP_CASE
242
253
  default:
243
- rb_raise(rb_eCADataTypeError, "median: unsupported data_type %d", dt);
254
+ rb_raise(rb_eCADataTypeError, "median: source data_type :%s not supported", ca_type_name[dt]);
244
255
  }
245
256
  }
246
257
 
@@ -298,13 +309,13 @@ median_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
298
309
  if ( RTEST(rb_ca_has_mask(self)) ) {
299
310
  CArray *mc; GetCArray(self, mc);
300
311
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
301
- if ( cnt < min_count ) filled = 1;
312
+ if ( ca_too_few_present(cnt, min_count, 1) ) filled = 1;
302
313
  else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
303
314
  }
304
315
  if ( !filled ) {
305
316
  CArray *sc;
306
317
  GetCArray(src, sc);
307
- if ( sc->elements == 0 ) filled = 1;
318
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) filled = 1;
308
319
  }
309
320
 
310
321
  if ( filled ) {
@@ -348,15 +359,65 @@ mp_object_interpolates (VALUE v)
348
359
  }
349
360
  if ( rb_obj_is_kind_of(v, rb_cCArray) ) {
350
361
  CArray *ca;
362
+ ca_size_t i;
351
363
  GetCArray(v, ca);
352
- if ( ca->elements == 0 ) {
353
- return 1;
364
+ for ( i = 0; i < ca->elements; i++ ) {
365
+ VALUE e = rb_ca_fetch_addr(v, i);
366
+ if ( e != CA_UNDEF && ! rb_obj_is_kind_of(e, rb_cNumeric) ) {
367
+ return 0;
368
+ }
354
369
  }
355
- return rb_obj_is_kind_of(rb_ca_fetch_addr(v, 0), rb_cNumeric) ? 1 : 0;
370
+ return 1;
356
371
  }
357
372
  return 0;
358
373
  }
359
374
 
375
+ /* [object] The element an interpolation refusal names: lo itself, or the
376
+ first element of a CArray of them that has no arithmetic. */
377
+ static VALUE
378
+ mp_first_non_numeric (VALUE v)
379
+ {
380
+ if ( rb_obj_is_kind_of(v, rb_cCArray) ) {
381
+ CArray *ca;
382
+ ca_size_t i;
383
+ GetCArray(v, ca);
384
+ for ( i = 0; i < ca->elements; i++ ) {
385
+ VALUE e = rb_ca_fetch_addr(v, i);
386
+ if ( e != CA_UNDEF && ! rb_obj_is_kind_of(e, rb_cNumeric) ) {
387
+ return e;
388
+ }
389
+ }
390
+ }
391
+ return v;
392
+ }
393
+
394
+ /* [object] An order statistic answers in the elements' own terms: a pick
395
+ is the element itself, and an average or an interpolation is done with
396
+ the elements' own arithmetic and an exact weight, so a Rational or a
397
+ BigDecimal stays exact and a Float stays a Float. */
398
+
399
+ /* [object] the weight of the upper element at p, exact: (n-1)*p/100 - k as
400
+ a Rational, with p read as the decimal it was written as. The double r
401
+ carries the rounding of (n-1)*p/100 (p = 30 over five elements is
402
+ 0.19999999999999996 there, not 1/5), so it is not used for the weight. */
403
+ static VALUE
404
+ obj_weight (long n, double p, long k)
405
+ {
406
+ VALUE pr = rb_funcall(DBL2NUM(p), id_rationalize, 0);
407
+ VALUE f = rb_funcall(rb_funcall(pr, id_mul, 1, LONG2NUM(n - 1)), rb_intern("quo"), 1, INT2FIX(100));
408
+ return rb_funcall(f, rb_intern("-"), 1, LONG2NUM(k));
409
+ }
410
+
411
+ /* The midpoint of lo and hi, (lo + hi) * (1/2): exact for an Integer or a
412
+ Rational, and the same for a cell (the flat path) and for a CArray of
413
+ them (the per-axis path). */
414
+ static VALUE
415
+ obj_midpoint (VALUE lo, VALUE hi)
416
+ {
417
+ return rb_funcall(rb_funcall(lo, id_plus, 1, hi), id_mul, 1,
418
+ rb_rational_new(INT2FIX(1), INT2FIX(2)));
419
+ }
420
+
360
421
  /* [object] Refuse to interpolate between two elements that have no
361
422
  arithmetic meaning it. A String has both a `*` and a `+`, and
362
423
  String#* truncates the Float weight to zero -- so :linear quietly
@@ -370,12 +431,10 @@ mp_require_interpolable (const char *who, VALUE method, VALUE lo)
370
431
  return;
371
432
  }
372
433
  rb_raise(rb_eCADataTypeError,
373
- "%s: method: :%s interpolates between two elements, which %s "
374
- "does not support; use method: :lower, :higher or :nearest to "
375
- "pick an element instead",
376
- who, rb_id2name(SYM2ID(method)),
377
- rb_obj_classname(rb_obj_is_kind_of(lo, rb_cCArray)
378
- ? rb_ca_fetch_addr(lo, 0) : lo));
434
+ "%s: method: :%"PRIsVALUE" interpolates between two elements, "
435
+ "which %"PRIsVALUE" does not support; use method: :lower, :higher "
436
+ "or :nearest to pick an element instead",
437
+ who, rb_sym2str(method), rb_obj_class(mp_first_non_numeric(lo)));
379
438
  }
380
439
 
381
440
  /* [object] recv.mid(axis: raxis) -- e.g. min/max/sort along an axis. */
@@ -433,7 +492,8 @@ obj_kth_pair (VALUE self, long axis, long k, long n, VALUE *lo, VALUE *hi)
433
492
  }
434
493
 
435
494
  /* [object] keep_axis flat: [1,...,1] entity holding val (UNDEF assigns
436
- masked). Uses []= so UNDEF / arbitrary objects store correctly. */
495
+ masked), in self's Face when self is one. Uses []= so UNDEF / arbitrary
496
+ objects store correctly. */
437
497
  static VALUE
438
498
  obj_keep_axis_full (VALUE self, VALUE val)
439
499
  {
@@ -448,7 +508,7 @@ obj_keep_axis_full (VALUE self, VALUE val)
448
508
  for ( int i = 0; i < ndim; i++ ) av[i] = INT2FIX(0);
449
509
  av[ndim] = val;
450
510
  rb_funcallv(out, id_aset, ndim + 1, av);
451
- return out;
511
+ return ca_is_face(ca) ? ca_face_relift_entity(self, out) : out;
452
512
  }
453
513
 
454
514
  /* [object] per-axis median (= reduced CArray; 1-D self -> bare scalar).
@@ -467,23 +527,50 @@ median_object_axis (VALUE self, long axis, long n, int keep_axis)
467
527
  if ( ! mp_object_interpolates(lo) ) {
468
528
  rb_raise(rb_eCADataTypeError,
469
529
  "median: an even number of elements has no middle one, and "
470
- "%s cannot be averaged; use percentile(50, method: :lower) "
471
- "(or :higher / :nearest) to pick an element instead",
472
- rb_obj_classname(rb_obj_is_kind_of(lo, rb_cCArray)
473
- ? rb_ca_fetch_addr(lo, 0) : lo));
530
+ "%"PRIsVALUE" cannot be averaged; use percentile(50, method: "
531
+ ":lower) (or :higher / :nearest) to pick an element instead",
532
+ rb_obj_class(mp_first_non_numeric(lo)));
474
533
  }
475
- result = rb_funcall(rb_funcall(lo, id_plus, 1, hi), id_div, 1, DBL2NUM(2.0));
534
+ result = obj_midpoint(lo, hi);
476
535
  } else {
477
536
  VALUE kv = obj_kth_one(self, axis, (n - 1) / 2);
478
- result = rb_funcall(kv, id_mul, 1, DBL2NUM(1.0)); /* force float promote */
537
+ result = kv;
479
538
  }
480
- if ( keep_axis ) {
539
+ /* a 1-D self reduces to a bare object; mp_axis_answer keeps it */
540
+ if ( keep_axis && rb_obj_is_kind_of(result, rb_cCArray) ) {
481
541
  VALUE ia[1] = { LONG2NUM(axis) };
482
542
  result = rb_ca_insert_axis(1, ia, result);
483
543
  }
484
544
  return result;
485
545
  }
486
546
 
547
+ /* [entry] An axis form's answer in the shape the other reductions give it.
548
+ A reduction that removes the only axis of a 1-D array is a bare value, as
549
+ sum(axis: 0) is, and keep_axis: makes it a one-cell array; the lanes answer
550
+ either way, so the answer is shaped here. An answer the object lane took
551
+ as a slice of its partitioned copy is made an array of its own, as the
552
+ other reductions' answers are. */
553
+ static VALUE
554
+ mp_axis_answer (VALUE self, VALUE res, int keep_axis)
555
+ {
556
+ CArray *ca;
557
+ GetCArray(self, ca);
558
+ int is_array = RTEST(rb_obj_is_kind_of(res, rb_cCArray));
559
+ if ( ca->ndim != 1 ) {
560
+ if ( is_array ) {
561
+ CArray *cr;
562
+ GetCArray(res, cr);
563
+ if ( ! ca_is_entity(cr) && ! ca_is_face(cr) ) res = rb_funcall(res, id_copy, 0);
564
+ }
565
+ return res;
566
+ }
567
+ if ( keep_axis ) {
568
+ if ( is_array ) return res;
569
+ return obj_keep_axis_full(self, res);
570
+ }
571
+ return is_array ? rb_funcall(res, id_aref, 1, INT2FIX(0)) : res;
572
+ }
573
+
487
574
  /* [object] flat median: mask-strip / min_count, flatten, axis-0 reduce. */
488
575
  static VALUE
489
576
  median_object_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
@@ -493,13 +580,13 @@ median_object_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
493
580
  if ( RTEST(rb_ca_has_mask(self)) ) {
494
581
  CArray *mc; GetCArray(self, mc);
495
582
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
496
- if ( cnt < min_count ) filled = 1;
583
+ if ( ca_too_few_present(cnt, min_count, 1) ) filled = 1;
497
584
  else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
498
585
  }
499
586
  if ( !filled ) {
500
587
  CArray *sc;
501
588
  GetCArray(src, sc);
502
- if ( sc->elements == 0 ) filled = 1;
589
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) filled = 1;
503
590
  }
504
591
  if ( filled ) {
505
592
  result = NIL_P(fill_value) ? CA_UNDEF : fill_value;
@@ -517,31 +604,28 @@ median_object_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
517
604
  pre-fetched). Reached ONLY by CA_OBJECT percentile/quantile -- never
518
605
  by numeric (that path uses pct_compute) and never by median.
519
606
  The :linear / :midpoint interpolation funcalls are irreducible for
520
- arbitrary objects; the *1.0 in the pick methods is float-promotion to
521
- match the numeric "always Float" output contract. */
607
+ arbitrary objects; the pick methods return the element itself. */
522
608
  static VALUE
523
- pct_compute_object (VALUE method, long k, double r, long n, VALUE lo, VALUE hi)
609
+ pct_compute_object (VALUE method, double p, long k, double r, long n, VALUE lo, VALUE hi)
524
610
  {
525
- if ( method == sym_lower ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
526
- if ( method == sym_higher ) {
527
- VALUE v = (r == 0.0) ? lo : hi;
528
- return rb_funcall(v, id_mul, 1, DBL2NUM(1.0));
529
- }
611
+ if ( method == sym_lower ) return lo;
612
+ if ( method == sym_higher ) return (r == 0.0) ? lo : hi;
530
613
  if ( method == sym_nearest ) {
531
614
  int use_k = (r < 0.5) || (r == 0.5 && (k % 2 == 0));
532
- return rb_funcall(use_k ? lo : hi, id_mul, 1, DBL2NUM(1.0));
615
+ return use_k ? lo : hi;
533
616
  }
534
617
  if ( method == sym_linear ) {
535
- if ( r == 0.0 || k + 1 >= n ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
618
+ if ( r == 0.0 || k + 1 >= n ) return lo;
536
619
  mp_require_interpolable("percentile", method, lo);
537
- VALUE a = rb_funcall(lo, id_mul, 1, DBL2NUM(1.0 - r));
538
- VALUE b = rb_funcall(hi, id_mul, 1, DBL2NUM(r));
620
+ VALUE w = obj_weight(n, p, k);
621
+ VALUE a = rb_funcall(lo, id_mul, 1, rb_funcall(INT2FIX(1), rb_intern("-"), 1, w));
622
+ VALUE b = rb_funcall(hi, id_mul, 1, w);
539
623
  return rb_funcall(a, id_plus, 1, b);
540
624
  }
541
625
  if ( method == sym_midpoint ) {
542
- if ( k + 1 >= n ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
626
+ if ( k + 1 >= n ) return lo;
543
627
  mp_require_interpolable("percentile", method, lo);
544
- return rb_funcall(rb_funcall(lo, id_plus, 1, hi), id_div, 1, DBL2NUM(2.0));
628
+ return obj_midpoint(lo, hi);
545
629
  }
546
630
  rb_raise(rb_eArgError, "percentile: invalid method (BUG)");
547
631
  }
@@ -550,11 +634,8 @@ pct_compute_object (VALUE method, long k, double r, long n, VALUE lo, VALUE hi)
550
634
  static VALUE
551
635
  pct_object_one_partition (VALUE self, long axis, long n, double p, VALUE method)
552
636
  {
553
- if ( p == 100.0 )
554
- return rb_funcall(obj_call_axis(self, id_max, LONG2NUM(axis)),
555
- id_mul, 1, DBL2NUM(1.0));
556
637
  if ( n == 1 )
557
- return rb_funcall(obj_slice(self, axis, 0), id_mul, 1, DBL2NUM(1.0));
638
+ return obj_slice(self, axis, 0);
558
639
  double f = (n - 1) * p / 100.0;
559
640
  long k = (long) floor(f);
560
641
  double r = f - k;
@@ -562,7 +643,7 @@ pct_object_one_partition (VALUE self, long axis, long n, double p, VALUE method)
562
643
  VALUE lo = Qnil, hi = Qnil;
563
644
  if ( need == PCT_LOWER_ONLY ) lo = obj_kth_one(self, axis, k);
564
645
  else obj_kth_pair(self, axis, k, n, &lo, &hi);
565
- return pct_compute_object(method, k, r, n, lo, hi);
646
+ return pct_compute_object(method, p, k, r, n, lo, hi);
566
647
  }
567
648
 
568
649
  /* [object] single p from a fully sorted entity (multi-p shared sort). */
@@ -570,9 +651,9 @@ static VALUE
570
651
  pct_object_one_sorted (VALUE sorted, long axis, long n, double p, VALUE method)
571
652
  {
572
653
  if ( p == 100.0 )
573
- return rb_funcall(obj_slice(sorted, axis, n - 1), id_mul, 1, DBL2NUM(1.0));
654
+ return obj_slice(sorted, axis, n - 1);
574
655
  if ( n == 1 )
575
- return rb_funcall(obj_slice(sorted, axis, 0), id_mul, 1, DBL2NUM(1.0));
656
+ return obj_slice(sorted, axis, 0);
576
657
  double f = (n - 1) * p / 100.0;
577
658
  long k = (long) floor(f);
578
659
  double r = f - k;
@@ -581,7 +662,7 @@ pct_object_one_sorted (VALUE sorted, long axis, long n, double p, VALUE method)
581
662
  VALUE lo = Qnil, hi = Qnil;
582
663
  if ( need != PCT_UPPER_ONLY ) lo = obj_slice(sorted, axis, k);
583
664
  if ( need != PCT_LOWER_ONLY ) hi = obj_slice(sorted, axis, kup);
584
- return pct_compute_object(method, k, r, n, lo, hi);
665
+ return pct_compute_object(method, p, k, r, n, lo, hi);
585
666
  }
586
667
 
587
668
  /* [object] per-axis percentile -> array of (reduced CArray | scalar),
@@ -633,7 +714,9 @@ pct_object_axis_masked_one (VALUE self, VALUE sorted, long axis, double p,
633
714
  VALUE fill_value, int keep_axis)
634
715
  {
635
716
  int8_t ax = (int8_t) axis;
636
- VALUE out = rb_ca_new_reduced(self, &ax, 1, CA_OBJECT, keep_axis);
717
+ /* `sorted` is an entity of self's shape (the storage, when self is a
718
+ Face); the answer is built on it and lifted back by the caller. */
719
+ VALUE out = rb_ca_new_reduced(sorted, &ax, 1, CA_OBJECT, keep_axis);
637
720
  CArray *co;
638
721
  GetCArray(out, co);
639
722
  CArray *cs;
@@ -652,7 +735,7 @@ pct_object_axis_masked_one (VALUE self, VALUE sorted, long axis, double p,
652
735
  for ( long j = 0; j < M; j++ )
653
736
  if ( !sm[f0 + (ca_size_t)(j * INNER)] ) np++;
654
737
  }
655
- if ( np == 0 || np < min_count ) {
738
+ if ( ca_too_few_present(np, min_count, 1) ) {
656
739
  if ( is_undef ) {
657
740
  if ( !co->mask ) ca_create_mask(co);
658
741
  ((boolean8_t *) co->mask->ptr)[oc] = 1;
@@ -670,7 +753,7 @@ pct_object_axis_masked_one (VALUE self, VALUE sorted, long axis, double p,
670
753
  VALUE lo = Qnil, hi = Qnil;
671
754
  if ( need != PCT_UPPER_ONLY ) lo = sb[f0 + (ca_size_t)(k * INNER)];
672
755
  if ( need != PCT_LOWER_ONLY ) hi = sb[f0 + (ca_size_t)(kup * INNER)];
673
- op[oc] = pct_compute_object(method, k, r, np, lo, hi);
756
+ op[oc] = pct_compute_object(method, p, k, r, np, lo, hi);
674
757
  }
675
758
  }
676
759
  return out;
@@ -695,7 +778,11 @@ percentile_object_axis_masked (VALUE self, VALUE pers, long axis, VALUE method,
695
778
  return res;
696
779
  }
697
780
 
781
+ /* A Face (a string array) is sorted as itself and read through its
782
+ storage, whose cells are the values. */
698
783
  VALUE sorted = rb_funcall(obj_call_axis(self, id_sort, LONG2NUM(axis)), id_copy, 0);
784
+ int is_face = ca_is_face(ca);
785
+ if ( is_face ) sorted = rb_ca_strip_face_value(sorted);
699
786
  CArray *cs;
700
787
  GetCArray(sorted, cs);
701
788
  const boolean8_t *sm = cs->mask ? (const boolean8_t *) cs->mask->ptr : NULL;
@@ -706,10 +793,10 @@ percentile_object_axis_masked (VALUE self, VALUE pers, long axis, VALUE method,
706
793
  VALUE result = rb_ary_new_capa(npers);
707
794
  for ( long i = 0; i < npers; i++ ) {
708
795
  double p = NUM2DBL(rb_ary_entry(pers, i));
709
- rb_ary_push(result,
710
- pct_object_axis_masked_one(self, sorted, axis, p, method,
711
- OUTER, MM, INNER, sm, min_count,
712
- fill_value, keep_axis));
796
+ VALUE one = pct_object_axis_masked_one(self, sorted, axis, p, method,
797
+ OUTER, MM, INNER, sm, min_count,
798
+ fill_value, keep_axis);
799
+ rb_ary_push(result, is_face ? ca_face_relift_entity(self, one) : one);
713
800
  }
714
801
  RB_GC_GUARD(sorted);
715
802
  return result;
@@ -738,13 +825,13 @@ percentile_object_flat (VALUE self, VALUE pers, long min_count, VALUE fill_value
738
825
  if ( RTEST(rb_ca_has_mask(self)) ) {
739
826
  CArray *mc; GetCArray(self, mc);
740
827
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
741
- if ( cnt < min_count ) filled = 1;
828
+ if ( ca_too_few_present(cnt, min_count, 1) ) filled = 1;
742
829
  else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
743
830
  }
744
831
  if ( !filled ) {
745
832
  CArray *sc;
746
833
  GetCArray(src, sc);
747
- if ( sc->elements == 0 ) filled = 1;
834
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) filled = 1;
748
835
  }
749
836
  if ( filled ) {
750
837
  VALUE fill = NIL_P(fill_value) ? CA_UNDEF : fill_value;
@@ -765,7 +852,7 @@ static VALUE median_axis_masked (VALUE self, long axis, long min_count,
765
852
  /* [entry] median(axis:, min_count:, fill_value:, keep_axis:). Dispatches
766
853
  fixlen -> reject, object -> object lane, numeric -> numeric lane. */
767
854
  static VALUE
768
- rb_ca_median_m (int argc, VALUE *argv, VALUE self)
855
+ rb_ca_median_m_body (int argc, VALUE *argv, VALUE self)
769
856
  {
770
857
  VALUE ropt = rb_pop_options(&argc, &argv);
771
858
  VALUE raxis = Qnil, rmin_count = INT2FIX(0), fill_value = Qnil, rkeep = Qfalse;
@@ -776,6 +863,15 @@ rb_ca_median_m (int argc, VALUE *argv, VALUE self)
776
863
 
777
864
  CArray *ca;
778
865
  GetCArray(self, ca);
866
+ /* A lazy expression is computed whole here, so a registered evaluator
867
+ may compute it instead. */
868
+ {
869
+ volatile VALUE evaluated = ca_lazy_evaluated(self);
870
+ if ( ! NIL_P(evaluated) ) {
871
+ self = evaluated;
872
+ GetCArray(self, ca);
873
+ }
874
+ }
779
875
  /* Boolean rides the f64 lane: 0/1 -> 0.0/1.0, so median interpolates and
780
876
  returns a float (a 2-element median averages to 0.5), matching how the
781
877
  numeric lane treats integer input. */
@@ -784,17 +880,25 @@ rb_ca_median_m (int argc, VALUE *argv, VALUE self)
784
880
  GetCArray(self, ca);
785
881
  }
786
882
  int8_t dt = ca->data_type;
787
- if ( ca_is_fixlen_type(ca) )
883
+ if ( ca_is_fixlen_type(ca) ) {
884
+ ca_face_refuse_numeric(self, "median", rb_eCADataTypeError);
788
885
  rb_raise(rb_eCADataTypeError,
789
886
  "median: not defined for fixlen (no numeric midpoint); "
790
887
  "use a numeric or object array");
888
+ }
791
889
  int is_obj = (dt == CA_OBJECT);
792
890
  if ( !is_obj && !mp_is_numeric(dt) )
793
- rb_raise(rb_eCADataTypeError, "median: unsupported data_type %d", dt);
891
+ rb_raise(rb_eCADataTypeError, "median: source data_type :%s not supported", ca_type_name[dt]);
794
892
 
795
- long min_count = NUM2LONG(rmin_count);
796
- if ( min_count < 0 )
797
- rb_raise(rb_eArgError, "min_count must be non-negative; got %ld", min_count);
893
+ long min_count = ca_min_count_arg(rmin_count, NULL);
894
+ /* fill_value: UNDEF leaves the result undefined; any other fill is what
895
+ a store into the float64 result would make of it. */
896
+ if ( fill_value == CA_UNDEF ) {
897
+ fill_value = Qnil;
898
+ }
899
+ else if ( ! NIL_P(fill_value) && ! is_obj ) {
900
+ fill_value = ca_fill_as(fill_value, CA_FLOAT64);
901
+ }
798
902
  int keep_axis = RTEST(rkeep);
799
903
 
800
904
  if ( !NIL_P(raxis) ) {
@@ -802,16 +906,23 @@ rb_ca_median_m (int argc, VALUE *argv, VALUE self)
802
906
  long n = (long) ca->dim[axis];
803
907
  if ( n == 0 ) return mp_axis_all_masked(self, axis, keep_axis);
804
908
  /* mask / min_count / fill_value require the per-fiber select (each fiber
805
- has its own n_present); the plain partition path assumes a uniform n. */
806
- if ( RTEST(rb_ca_has_mask(self)) || min_count > 0 || !NIL_P(fill_value) )
807
- return is_obj
909
+ has its own n_present); the plain partition path assumes a uniform n.
910
+ Switch ca_fast_paths_enabled(): off, the per-fiber select. */
911
+ VALUE res;
912
+ if ( RTEST(rb_ca_has_mask(self)) || min_count > 0 || !NIL_P(fill_value)
913
+ || ! ca_fast_paths_enabled() )
914
+ res = is_obj
808
915
  ? median_object_axis_masked(self, axis, min_count, fill_value, keep_axis)
809
916
  : median_axis_masked(self, axis, min_count, fill_value, keep_axis);
810
- return is_obj ? median_object_axis(self, axis, n, keep_axis)
811
- : median_axis(self, axis, n, keep_axis);
917
+ else
918
+ res = is_obj ? median_object_axis(self, axis, n, keep_axis)
919
+ : median_axis(self, axis, n, keep_axis);
920
+ return mp_axis_answer(self, res, keep_axis);
812
921
  }
813
- return is_obj ? median_object_flat(self, min_count, fill_value, keep_axis)
814
- : median_flat(self, min_count, fill_value, keep_axis);
922
+ /* self may be the float64 view made above, read through its CArray */
923
+ CA_RETURN_KEEPING(self,
924
+ is_obj ? median_object_flat(self, min_count, fill_value, keep_axis)
925
+ : median_flat(self, min_count, fill_value, keep_axis));
815
926
  }
816
927
 
817
928
  /* =====================================================================
@@ -858,7 +969,7 @@ pct_fill (double *op, int8_t dt, const char *base, int from_sorted,
858
969
  MP_TYPES(MP_CASE)
859
970
  #undef MP_CASE
860
971
  default:
861
- rb_raise(rb_eCADataTypeError, "percentile: unsupported data_type %d", dt);
972
+ rb_raise(rb_eCADataTypeError, "percentile: source data_type :%s not supported", ca_type_name[dt]);
862
973
  }
863
974
  }
864
975
 
@@ -886,7 +997,7 @@ pct_fill (double *op, int8_t dt, const char *base, int from_sorted,
886
997
  for ( long j = 0; j < M; j++ ) \
887
998
  if ( !sm[f0 + (ca_size_t)(j * INNER)] ) np++; \
888
999
  } \
889
- if ( np == 0 || np < min_count ) { \
1000
+ if ( ca_too_few_present(np, min_count, 1) ) { \
890
1001
  if ( is_undef ) { \
891
1002
  if ( !co->mask ) ca_create_mask(co); \
892
1003
  ((boolean8_t *) co->mask->ptr)[oc] = 1; \
@@ -927,7 +1038,7 @@ pct_fill_masked (CArray *co, int8_t dt, const char *base, const boolean8_t *sm,
927
1038
  MP_TYPES(MP_CASE)
928
1039
  #undef MP_CASE
929
1040
  default:
930
- rb_raise(rb_eCADataTypeError, "percentile: unsupported data_type %d", dt);
1041
+ rb_raise(rb_eCADataTypeError, "percentile: source data_type :%s not supported", ca_type_name[dt]);
931
1042
  }
932
1043
  }
933
1044
 
@@ -1116,7 +1227,7 @@ pct_flat (VALUE self, VALUE pers_ary, long min_count, VALUE fill_value,
1116
1227
  if ( RTEST(rb_ca_has_mask(self)) ) {
1117
1228
  CArray *mc; GetCArray(self, mc);
1118
1229
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
1119
- if ( cnt < min_count ) {
1230
+ if ( ca_too_few_present(cnt, min_count, 1) ) {
1120
1231
  masked_out = 1; is_undef = NIL_P(fill_value);
1121
1232
  if ( !is_undef ) fillv = NUM2DBL(fill_value);
1122
1233
  } else {
@@ -1126,7 +1237,7 @@ pct_flat (VALUE self, VALUE pers_ary, long min_count, VALUE fill_value,
1126
1237
  if ( !masked_out ) {
1127
1238
  CArray *sc;
1128
1239
  GetCArray(src, sc);
1129
- if ( sc->elements == 0 ) {
1240
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) {
1130
1241
  masked_out = 1; is_undef = NIL_P(fill_value);
1131
1242
  if ( !is_undef ) fillv = NUM2DBL(fill_value);
1132
1243
  }
@@ -1174,7 +1285,10 @@ pct_flatten_validate_pers (VALUE pers)
1174
1285
  rb_raise(rb_eArgError, "percentile: at least one p value required");
1175
1286
  for ( long i = 0; i < len; i++ ) {
1176
1287
  VALUE p = rb_ary_entry(pers, i);
1177
- if ( !rb_obj_is_kind_of(p, rb_cNumeric) || NUM2DBL(p) < 0.0 || NUM2DBL(p) > 100.0 )
1288
+ /* Written as "inside", so that NaN -- which fails every comparison --
1289
+ is refused rather than let through. */
1290
+ if ( !rb_obj_is_kind_of(p, rb_cNumeric) ||
1291
+ !(NUM2DBL(p) >= 0.0 && NUM2DBL(p) <= 100.0) )
1178
1292
  rb_raise(rb_eArgError,
1179
1293
  "percentile: p must be Numeric in [0,100] (got %"PRIsVALUE")",
1180
1294
  rb_inspect(p));
@@ -1186,9 +1300,8 @@ pct_flatten_validate_pers (VALUE pers)
1186
1300
  keep_axis:). Dispatches fixlen -> reject, object -> object lane,
1187
1301
  numeric -> numeric lane. */
1188
1302
  static VALUE
1189
- rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1303
+ rb_ca_percentile_kw (int argc, VALUE *argv, VALUE ropt, VALUE self)
1190
1304
  {
1191
- VALUE ropt = rb_pop_options(&argc, &argv);
1192
1305
  VALUE raxis = Qnil, rmin_count = INT2FIX(0), fill_value = Qnil,
1193
1306
  method = sym_linear, rkeep = Qfalse;
1194
1307
  rb_scan_options(ropt, "axis,min_count,fill_value,method,keep_axis",
@@ -1196,6 +1309,15 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1196
1309
 
1197
1310
  CArray *ca;
1198
1311
  GetCArray(self, ca);
1312
+ /* A lazy expression is computed whole here, so a registered evaluator
1313
+ may compute it instead. */
1314
+ {
1315
+ volatile VALUE evaluated = ca_lazy_evaluated(self);
1316
+ if ( ! NIL_P(evaluated) ) {
1317
+ self = evaluated;
1318
+ GetCArray(self, ca);
1319
+ }
1320
+ }
1199
1321
  /* Boolean rides the f64 lane (0/1 -> 0.0/1.0), so percentile / quantile
1200
1322
  interpolate and return a float, matching the integer lane. */
1201
1323
  if ( ca->data_type == CA_BOOLEAN ) {
@@ -1203,16 +1325,24 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1203
1325
  GetCArray(self, ca);
1204
1326
  }
1205
1327
  int8_t dt = ca->data_type;
1206
- if ( ca_is_fixlen_type(ca) )
1328
+ if ( ca_is_fixlen_type(ca) ) {
1329
+ ca_face_refuse_numeric(self, "percentile", rb_eCADataTypeError);
1207
1330
  rb_raise(rb_eCADataTypeError,
1208
1331
  "percentile: not defined for fixlen; use a numeric or object array");
1332
+ }
1209
1333
  int is_obj = (dt == CA_OBJECT);
1210
1334
  if ( !is_obj && !mp_is_numeric(dt) )
1211
- rb_raise(rb_eCADataTypeError, "percentile: unsupported data_type %d", dt);
1335
+ rb_raise(rb_eCADataTypeError, "percentile: source data_type :%s not supported", ca_type_name[dt]);
1212
1336
 
1213
- long min_count = NUM2LONG(rmin_count);
1214
- if ( min_count < 0 )
1215
- rb_raise(rb_eArgError, "min_count must be non-negative; got %ld", min_count);
1337
+ long min_count = ca_min_count_arg(rmin_count, NULL);
1338
+ /* fill_value: UNDEF leaves the result undefined; any other fill is what
1339
+ a store into the float64 result would make of it. */
1340
+ if ( fill_value == CA_UNDEF ) {
1341
+ fill_value = Qnil;
1342
+ }
1343
+ else if ( ! NIL_P(fill_value) && ! is_obj ) {
1344
+ fill_value = ca_fill_as(fill_value, CA_FLOAT64);
1345
+ }
1216
1346
 
1217
1347
  VALUE pers = pct_flatten_validate_pers(rb_ary_new_from_values(argc, argv));
1218
1348
  pct_validate_method(method);
@@ -1230,12 +1360,13 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1230
1360
  flattened first and follows the same rule (length 1 unwraps). */
1231
1361
  VALUE result;
1232
1362
  if ( !NIL_P(raxis) ) {
1233
- long axis = rb_ca_normalize_axis_value(self, raxis, "percentile");
1363
+ long axis = rb_ca_normalize_axis_value(self, raxis, NULL);
1234
1364
  /* mask / min_count / fill_value require the per-fiber select (each fiber
1235
1365
  has its own n_present); the plain partition/sort path assumes a
1236
- uniform n. These paths bake keep_axis into the reduced output. */
1366
+ uniform n. These paths bake keep_axis into the reduced output.
1367
+ Switch ca_fast_paths_enabled(): off, the per-fiber select. */
1237
1368
  int per_fiber = RTEST(rb_ca_has_mask(self)) || min_count > 0
1238
- || !NIL_P(fill_value);
1369
+ || !NIL_P(fill_value) || ! ca_fast_paths_enabled();
1239
1370
  if ( !is_obj ) {
1240
1371
  result = per_fiber
1241
1372
  ? pct_axis_masked(self, pers, axis, method, min_count, fill_value, keep_axis)
@@ -1245,7 +1376,7 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1245
1376
  min_count, fill_value, keep_axis);
1246
1377
  } else {
1247
1378
  result = percentile_object_axis(self, pers, axis, method);
1248
- if ( keep_axis ) {
1379
+ if ( keep_axis && ca->ndim > 1 ) {
1249
1380
  long len = RARRAY_LEN(result);
1250
1381
  VALUE w = rb_ary_new_capa(len);
1251
1382
  VALUE ia[1] = { LONG2NUM(axis) };
@@ -1254,6 +1385,11 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1254
1385
  result = w;
1255
1386
  }
1256
1387
  }
1388
+ {
1389
+ long len = RARRAY_LEN(result);
1390
+ for ( long i = 0; i < len; i++ )
1391
+ rb_ary_store(result, i, mp_axis_answer(self, rb_ary_entry(result, i), keep_axis));
1392
+ }
1257
1393
  } else if ( !is_obj ) {
1258
1394
  result = pct_flat(self, pers, min_count, fill_value, method, keep_axis);
1259
1395
  } else {
@@ -1266,16 +1402,25 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1266
1402
  result = w;
1267
1403
  }
1268
1404
  }
1405
+ /* self may be the float64 view made above, read through its CArray */
1406
+ RB_GC_GUARD(self);
1269
1407
 
1270
1408
  if ( single_p ) return rb_ary_entry(result, 0);
1271
1409
  return result;
1272
1410
  }
1273
1411
 
1412
+ static VALUE
1413
+ rb_ca_percentile_m_body (int argc, VALUE *argv, VALUE self)
1414
+ {
1415
+ VALUE ropt = rb_pop_options(&argc, &argv);
1416
+ return rb_ca_percentile_kw(argc, argv, ropt, self);
1417
+ }
1418
+
1274
1419
  /* [entry] quantile(axis:, keep_axis:) = percentile(0, 25, 50, 75, 100, ...).
1275
1420
  Return shape: Array<Float> len 5 (flat) or Array<CArray> len 5 (per-axis),
1276
1421
  matching percentile's multi-p wrapping since we pass 5 fixed p values. */
1277
1422
  static VALUE
1278
- rb_ca_quantile_m (int argc, VALUE *argv, VALUE self)
1423
+ rb_ca_quantile_m_body (int argc, VALUE *argv, VALUE self)
1279
1424
  {
1280
1425
  VALUE ropt = rb_pop_options(&argc, &argv);
1281
1426
  if ( argc != 0 )
@@ -1285,9 +1430,62 @@ rb_ca_quantile_m (int argc, VALUE *argv, VALUE self)
1285
1430
  VALUE opts = rb_hash_new();
1286
1431
  if ( !NIL_P(raxis) ) rb_hash_aset(opts, ID2SYM(rb_intern("axis")), raxis);
1287
1432
  rb_hash_aset(opts, ID2SYM(rb_intern("keep_axis")), rkeep);
1288
- VALUE pa[6] = { INT2FIX(0), INT2FIX(25), INT2FIX(50), INT2FIX(75), INT2FIX(100),
1289
- opts };
1290
- return rb_ca_percentile_m(6, pa, self);
1433
+ VALUE pa[5] = { INT2FIX(0), INT2FIX(25), INT2FIX(50), INT2FIX(75), INT2FIX(100) };
1434
+ return rb_ca_percentile_kw(5, pa, opts, self);
1435
+ }
1436
+
1437
+
1438
+ typedef struct { int argc; VALUE *argv; VALUE self; } mp_median_call_t;
1439
+
1440
+ static VALUE
1441
+ mp_median_answer_body (VALUE p)
1442
+ {
1443
+ mp_median_call_t *c = (mp_median_call_t *) p;
1444
+ return rb_ca_median_m_body(c->argc, c->argv, c->self);
1445
+ }
1446
+
1447
+ /* An error raised by the methods this one calls names this one. */
1448
+ static VALUE
1449
+ rb_ca_median_m (int argc, VALUE *argv, VALUE self)
1450
+ {
1451
+ mp_median_call_t c = { argc, argv, self };
1452
+ return ca_answer_as_caller(mp_median_answer_body, (VALUE) &c);
1453
+ }
1454
+
1455
+
1456
+ typedef struct { int argc; VALUE *argv; VALUE self; } mp_percentile_call_t;
1457
+
1458
+ static VALUE
1459
+ mp_percentile_answer_body (VALUE p)
1460
+ {
1461
+ mp_percentile_call_t *c = (mp_percentile_call_t *) p;
1462
+ return rb_ca_percentile_m_body(c->argc, c->argv, c->self);
1463
+ }
1464
+
1465
+ /* An error raised by the methods this one calls names this one. */
1466
+ static VALUE
1467
+ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1468
+ {
1469
+ mp_percentile_call_t c = { argc, argv, self };
1470
+ return ca_answer_as_caller(mp_percentile_answer_body, (VALUE) &c);
1471
+ }
1472
+
1473
+
1474
+ typedef struct { int argc; VALUE *argv; VALUE self; } mp_quantile_call_t;
1475
+
1476
+ static VALUE
1477
+ mp_quantile_answer_body (VALUE p)
1478
+ {
1479
+ mp_quantile_call_t *c = (mp_quantile_call_t *) p;
1480
+ return rb_ca_quantile_m_body(c->argc, c->argv, c->self);
1481
+ }
1482
+
1483
+ /* An error raised by the methods this one calls names this one. */
1484
+ static VALUE
1485
+ rb_ca_quantile_m (int argc, VALUE *argv, VALUE self)
1486
+ {
1487
+ mp_quantile_call_t c = { argc, argv, self };
1488
+ return ca_answer_as_caller(mp_quantile_answer_body, (VALUE) &c);
1291
1489
  }
1292
1490
 
1293
1491
  void
@@ -1297,6 +1495,7 @@ Init_carray_median_percentile (void)
1297
1495
  id_sort_copy = rb_intern("sort_copy");
1298
1496
  id_is_not_masked = rb_intern("is_not_masked");
1299
1497
  id_plus = rb_intern("+"); id_div = rb_intern("/"); id_mul = rb_intern("*");
1498
+ id_rationalize = rb_intern("rationalize");
1300
1499
  id_min = rb_intern("min"); id_max = rb_intern("max");
1301
1500
  id_sort = rb_intern("sort"); id_copy = rb_intern("copy");
1302
1501
  id_aref = rb_intern("[]"); id_aset = rb_intern("[]=");