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.
- checksums.yaml +4 -4
- data/.yardopts +5 -25
- data/CHANGELOG.md +16 -0
- data/LICENSE +1 -1
- data/NEWS.md +3 -0
- data/README.md +128 -44
- data/carray.gemspec +22 -24
- data/ext/ca_array_pool.c +91 -0
- data/ext/ca_axis_descriptor.h +186 -0
- data/ext/ca_axis_dispatch.c +924 -0
- data/ext/ca_axis_group.c +1208 -0
- data/ext/ca_bincmp_dispatch.c +76 -0
- data/ext/ca_bincmp_dispatch.h +85 -0
- data/ext/ca_binop_dispatch.c +125 -0
- data/ext/ca_binop_dispatch.h +159 -0
- data/ext/ca_categorical_iterator.c +1375 -0
- data/ext/ca_compare.c +94 -0
- data/ext/ca_compare.h +26 -0
- data/ext/ca_composite_dispatch.c +414 -0
- data/ext/ca_composite_dispatch.h +116 -0
- data/ext/ca_for_buffer.h +96 -0
- data/ext/ca_for_each_element.h +241 -0
- data/ext/ca_group_iter.c +304 -0
- data/ext/ca_iter_substrate.h +325 -0
- data/ext/ca_kernel_iterator.c +4321 -0
- data/ext/ca_kernel_iterator.h +2603 -0
- data/ext/ca_moncmp_dispatch.c +37 -0
- data/ext/ca_moncmp_dispatch.h +62 -0
- data/ext/ca_monop_dispatch.c +200 -0
- data/ext/ca_monop_dispatch.h +235 -0
- data/ext/ca_obj_array.c +355 -359
- data/ext/ca_obj_bincmp.c +809 -0
- data/ext/ca_obj_binop.c +892 -0
- data/ext/ca_obj_bitarray.c +369 -164
- data/ext/ca_obj_bitfield.c +294 -234
- data/ext/ca_obj_block.c +189 -711
- data/ext/ca_obj_byte_swap.c +766 -0
- data/ext/ca_obj_const_string.c +965 -0
- data/ext/ca_obj_face.c +670 -0
- data/ext/ca_obj_face.h +247 -0
- data/ext/ca_obj_fake.c +228 -100
- data/ext/ca_obj_farray.c +54 -441
- data/ext/ca_obj_field.c +82 -529
- data/ext/ca_obj_fixlen_string.c +306 -0
- data/ext/ca_obj_grid.c +858 -440
- data/ext/ca_obj_meld.c +1034 -0
- data/ext/ca_obj_moncmp.c +569 -0
- data/ext/ca_obj_monop.c +1111 -0
- data/ext/ca_obj_object.c +774 -298
- data/ext/ca_obj_record.c +468 -0
- data/ext/ca_obj_reduce.c +97 -82
- data/ext/ca_obj_refer.c +569 -459
- data/ext/ca_obj_remap.c +475 -0
- data/ext/ca_obj_repeat.c +92 -477
- data/ext/ca_obj_roll.c +616 -0
- data/ext/ca_obj_select.c +344 -296
- data/ext/ca_obj_select_axis.c +1296 -0
- data/ext/ca_obj_shift.c +230 -792
- data/ext/ca_obj_source.c +78 -0
- data/ext/ca_obj_stack.c +1173 -0
- data/ext/ca_obj_stride.c +2501 -0
- data/ext/ca_obj_string.c +268 -0
- data/ext/ca_obj_tile.c +614 -0
- data/ext/ca_obj_time.c +546 -0
- data/ext/ca_obj_timedelta.c +435 -0
- data/ext/ca_obj_transpose.c +62 -516
- data/ext/ca_obj_triop.c +746 -0
- data/ext/ca_obj_unbound_repeat.c +208 -241
- data/ext/ca_obj_window.c +1131 -563
- data/ext/ca_op_byte_swap.c +175 -0
- data/ext/ca_op_ipower.c +319 -0
- data/ext/ca_op_powi.h +88 -0
- data/ext/ca_sort_kernels.h +132 -0
- data/ext/ca_sweep_engine.c +430 -0
- data/ext/ca_sweep_engine.h +157 -0
- data/ext/ca_transform_common.c +228 -0
- data/ext/ca_triop_dispatch.c +55 -0
- data/ext/ca_triop_dispatch.h +62 -0
- data/ext/carray.h +795 -402
- data/ext/carray_access.c +831 -711
- data/ext/carray_attribute.c +98 -330
- data/ext/carray_bincount.c +255 -0
- data/ext/carray_broadcast.c +283 -0
- data/ext/carray_call_cfunc.c +1360 -828
- data/ext/carray_call_cfunc.h +160 -0
- data/ext/carray_cast.c +1212 -301
- data/ext/carray_cast_func.rb +81 -40
- data/ext/carray_class.c +53 -63
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +350 -346
- data/ext/carray_copy.c +156 -268
- data/ext/carray_core.c +1342 -199
- data/ext/carray_count.c +312 -0
- data/ext/carray_data_type.c +43 -19
- data/ext/carray_element.c +585 -213
- data/ext/carray_factorize.c +2542 -0
- data/ext/carray_generate.c +230 -559
- data/ext/carray_histogram.c +490 -0
- data/ext/carray_hold.c +228 -0
- data/ext/carray_index_classifier.c +1035 -0
- data/ext/carray_index_classifier.h +27 -0
- data/ext/carray_internal.h +120 -0
- data/ext/carray_kernels_bincmp.c +4445 -0
- data/ext/carray_kernels_binop.c +10979 -0
- data/ext/carray_kernels_init.c +36 -0
- data/ext/carray_kernels_map.c +3466 -0
- data/ext/carray_kernels_moncmp.c +2096 -0
- data/ext/carray_kernels_monop.c +18312 -0
- data/ext/carray_kernels_reduce_aggregate.c +25836 -0
- data/ext/carray_kernels_reduce_boolean.c +329 -0
- data/ext/carray_kernels_reduce_cumulative.c +14592 -0
- data/ext/carray_kernels_reduce_extreme.c +16947 -0
- data/ext/carray_kernels_reduce_variance.c +3909 -0
- data/ext/carray_kernels_scan.c +3692 -0
- data/ext/carray_kernels_search.c +32137 -0
- data/ext/carray_kernels_sort.c +10625 -0
- data/ext/carray_kernels_triop.c +1391 -0
- data/ext/carray_lazy.c +567 -0
- data/ext/carray_loop.c +88 -200
- data/ext/carray_mask.c +848 -154
- data/ext/carray_math_kernel.h +120 -0
- data/ext/carray_mathfunc.c +10 -241
- data/ext/carray_median_percentile.c +1257 -0
- data/ext/carray_memory_view.c +1625 -0
- data/ext/carray_operator.c +1526 -318
- data/ext/carray_order.c +664 -1394
- data/ext/carray_partition.c +416 -0
- data/ext/carray_random.c +518 -0
- data/ext/carray_scatter.c +357 -0
- data/ext/carray_slab.c +1219 -0
- data/ext/carray_slab.h +84 -0
- data/ext/carray_sort.c +829 -0
- data/ext/carray_sort_kernel.c +620 -0
- data/ext/carray_struct.c +695 -0
- data/ext/carray_test.c +343 -229
- data/ext/carray_undef.c +34 -17
- data/ext/carray_utils.c +175 -74
- data/ext/extconf.rb +216 -55
- data/ext/mk_call_cfunc.rb +480 -0
- data/ext/mkkernel.rb +8842 -0
- data/ext/ruby_carray.c +202 -101
- data/ext/version.h +4 -14
- data/ext/version.rb +5 -13
- data/lib/carray/arrow_tensor.rb +401 -0
- data/lib/carray/attribute.rb +166 -0
- data/lib/carray/autoload_carray.rb +220 -0
- data/lib/carray/autoload_method_extension.rb +44 -0
- data/lib/carray/axis_group.rb +711 -0
- data/lib/carray/basics.rb +481 -0
- data/lib/carray/bincount_nd.rb +358 -0
- data/lib/carray/block_iterator.rb +604 -0
- data/lib/carray/boolean_reduce.rb +109 -0
- data/lib/carray/categorical.rb +561 -0
- data/lib/carray/categorical_iterator.rb +1062 -0
- data/lib/carray/complex.rb +150 -0
- data/lib/carray/conditional.rb +216 -0
- data/lib/carray/const_string.rb +228 -0
- data/lib/carray/construct.rb +139 -328
- data/lib/carray/core_extensions.rb +240 -0
- data/lib/carray/data_type_extension.rb +233 -0
- data/lib/carray/fixlen_string.rb +95 -0
- data/lib/carray/frame/concat.rb +132 -0
- data/lib/carray/frame/convert.rb +95 -0
- data/lib/carray/frame/csv_parser.rb +211 -0
- data/lib/carray/frame/frame.rb +649 -0
- data/lib/carray/frame/group.rb +186 -0
- data/lib/carray/frame/io.rb +164 -0
- data/lib/carray/frame/join.rb +248 -0
- data/lib/carray/frame/records.rb +99 -0
- data/lib/carray/frame/sort.rb +113 -0
- data/lib/carray/frame/verbs.rb +299 -0
- data/lib/carray/frame.rb +16 -0
- data/lib/carray/histogram.rb +512 -0
- data/lib/carray/inspect.rb +37 -20
- data/lib/carray/iterator.rb +57 -349
- data/lib/carray/lazy.rb +889 -0
- data/lib/carray/mask_gap_fill.rb +200 -0
- data/lib/carray/math.rb +78 -342
- data/lib/carray/meld_reduce.rb +289 -0
- data/lib/carray/methods/align_addr.rb +116 -0
- data/lib/carray/methods/bin.rb +128 -0
- data/lib/carray/methods/bincount.rb +87 -0
- data/lib/carray/methods/bit_string.rb +92 -0
- data/lib/carray/methods/broadcast.rb +63 -0
- data/lib/carray/methods/choose.rb +39 -0
- data/lib/carray/methods/composition.rb +280 -0
- data/lib/carray/methods/gather_nd.rb +206 -0
- data/lib/carray/methods/index.rb +39 -0
- data/lib/carray/methods/insert_block.rb +99 -0
- data/lib/carray/methods/is_in.rb +141 -0
- data/lib/carray/methods/join.rb +90 -0
- data/lib/carray/methods/locate_addr.rb +47 -0
- data/lib/carray/methods/mask_duplicates.rb +41 -0
- data/lib/carray/methods/meshgrid.rb +91 -0
- data/lib/carray/methods/mode.rb +126 -0
- data/lib/carray/methods/nunique.rb +46 -0
- data/lib/carray/methods/resize.rb +56 -0
- data/lib/carray/methods/snap.rb +156 -0
- data/lib/carray/methods/string_format.rb +57 -0
- data/lib/carray/methods/unique.rb +47 -0
- data/lib/carray/methods/value_counts.rb +71 -0
- data/lib/carray/mkmf.rb +124 -101
- data/lib/carray/runtime.rb +108 -0
- data/lib/carray/serialize.rb +478 -167
- data/lib/carray/slab_iterator.rb +292 -0
- data/lib/carray/stack.rb +291 -0
- data/lib/carray/string.rb +56 -180
- data/lib/carray/string_operation_extension.rb +289 -0
- data/lib/carray/struct.rb +335 -323
- data/lib/carray/struct_builder.rb +697 -0
- data/lib/carray/table.rb +41 -2
- data/lib/carray/time.rb +2255 -38
- data/lib/carray/window_iterator.rb +655 -0
- data/lib/carray.rb +55 -57
- metadata +163 -130
- data/Rakefile +0 -51
- data/TODO.md +0 -18
- data/ext/ca_iter_block.c +0 -257
- data/ext/ca_iter_dimension.c +0 -299
- data/ext/ca_iter_window.c +0 -214
- data/ext/ca_obj_mapping.c +0 -644
- data/ext/carray_iterator.c +0 -641
- data/ext/carray_math.rb +0 -850
- data/ext/carray_numeric.c +0 -259
- data/ext/carray_sort_addr.c +0 -254
- data/ext/carray_stat.c +0 -2100
- data/ext/carray_stat_proc.rb +0 -1999
- data/ext/mkmath.rb +0 -741
- data/ext/ruby_ccomplex.c +0 -509
- data/ext/ruby_float_func.c +0 -86
- data/lib/carray/array.rb +0 -8
- data/lib/carray/autoload/autoload_base.rb +0 -19
- data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
- data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
- data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
- data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
- data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
- data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
- data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
- data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
- data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
- data/lib/carray/autoload/autoload_gem_random.rb +0 -8
- data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
- data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
- data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
- data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
- data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
- data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
- data/lib/carray/autoload/autoload_object_link.rb +0 -1
- data/lib/carray/autoload/autoload_object_pack.rb +0 -2
- data/lib/carray/autoload.rb +0 -141
- data/lib/carray/basic.rb +0 -191
- data/lib/carray/broadcast.rb +0 -101
- data/lib/carray/compose.rb +0 -315
- data/lib/carray/convert.rb +0 -115
- data/lib/carray/info.rb +0 -110
- data/lib/carray/io/imagemagick.rb +0 -235
- data/lib/carray/mask.rb +0 -102
- data/lib/carray/math/histogram.rb +0 -177
- data/lib/carray/math/recurrence.rb +0 -93
- data/lib/carray/object/ca_obj_iterator.rb +0 -50
- data/lib/carray/object/ca_obj_link.rb +0 -50
- data/lib/carray/object/ca_obj_pack.rb +0 -99
- data/lib/carray/obsolete.rb +0 -256
- data/lib/carray/ordering.rb +0 -181
- data/lib/carray/testing.rb +0 -51
- data/lib/carray/transform.rb +0 -109
- data/misc/Methods.ja.md +0 -182
- data/misc/NOTE +0 -51
- data/spec/Classes/CABitfield_spec.rb +0 -58
- data/spec/Classes/CABlockIterator_spec.rb +0 -114
- data/spec/Classes/CABlock_spec.rb +0 -205
- data/spec/Classes/CAField_spec.rb +0 -39
- data/spec/Classes/CAGrid_spec.rb +0 -75
- data/spec/Classes/CAMap_spec.rb +0 -0
- data/spec/Classes/CAMapping_spec.rb +0 -105
- data/spec/Classes/CAObject_attribute_spec.rb +0 -33
- data/spec/Classes/CAObject_spec.rb +0 -33
- data/spec/Classes/CARefer_spec.rb +0 -93
- data/spec/Classes/CARepeat_spec.rb +0 -65
- data/spec/Classes/CASelect_spec.rb +0 -22
- data/spec/Classes/CAShift_spec.rb +0 -16
- data/spec/Classes/CAStruct_spec.rb +0 -71
- data/spec/Classes/CATranspose_spec.rb +0 -60
- data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
- data/spec/Classes/CAWindow_spec.rb +0 -54
- data/spec/Classes/CAWrap_spec.rb +0 -8
- data/spec/Classes/CArray_spec.rb +0 -184
- data/spec/Classes/CScalar_spec.rb +0 -55
- data/spec/Classes/ex1.rb +0 -46
- data/spec/Features/feature_130_spec.rb +0 -19
- data/spec/Features/feature_attributes_spec.rb +0 -280
- data/spec/Features/feature_boolean_spec.rb +0 -98
- data/spec/Features/feature_broadcast.rb +0 -116
- data/spec/Features/feature_cast_function.rb +0 -19
- data/spec/Features/feature_cast_spec.rb +0 -33
- data/spec/Features/feature_class_spec.rb +0 -84
- data/spec/Features/feature_complex_spec.rb +0 -42
- data/spec/Features/feature_composite_spec.rb +0 -124
- data/spec/Features/feature_convert_spec.rb +0 -46
- data/spec/Features/feature_copy_spec.rb +0 -123
- data/spec/Features/feature_creation_spec.rb +0 -84
- data/spec/Features/feature_element_spec.rb +0 -144
- data/spec/Features/feature_extream_spec.rb +0 -54
- data/spec/Features/feature_generate_spec.rb +0 -74
- data/spec/Features/feature_index_spec.rb +0 -69
- data/spec/Features/feature_mask_spec.rb +0 -580
- data/spec/Features/feature_math_spec.rb +0 -97
- data/spec/Features/feature_order_spec.rb +0 -146
- data/spec/Features/feature_ref_store_spec.rb +0 -209
- data/spec/Features/feature_serialization_spec.rb +0 -125
- data/spec/Features/feature_stat_spec.rb +0 -397
- data/spec/Features/feature_virtual_spec.rb +0 -48
- data/spec/Features/method_eq_spec.rb +0 -81
- data/spec/Features/method_is_nan_spec.rb +0 -12
- data/spec/Features/method_map_spec.rb +0 -54
- data/spec/Features/method_max_with.rb +0 -20
- data/spec/Features/method_min_with.rb +0 -19
- data/spec/Features/method_ne_spec.rb +0 -18
- data/spec/Features/method_project_spec.rb +0 -188
- data/spec/Features/method_ref_spec.rb +0 -27
- data/spec/Features/method_round_spec.rb +0 -11
- data/spec/Features/method_s_linspace_spec.rb +0 -48
- data/spec/Features/method_s_span_spec.rb +0 -14
- data/spec/Features/method_seq_spec.rb +0 -47
- data/spec/Features/method_sort_with.rb +0 -43
- data/spec/Features/method_sorted_with.rb +0 -29
- data/spec/Features/method_span_spec.rb +0 -42
- data/spec/Features/method_wrap_readonly_spec.rb +0 -43
- data/spec/UnitTest/test_CAVirtual.rb +0 -214
- data/spec/spec_all.rb +0 -10
- data/utils/ca_ase.rb +0 -21
- data/utils/ca_methods.rb +0 -15
- data/utils/cast_checker.rb +0 -30
- data/utils/convert_test.rb +0 -73
- data/utils/extract_yard.rb +0 -22
- data/utils/guess_shape.rb +0 -76
- data/utils/monkey_patch_methods.rb +0 -62
- data/utils/remove_resource_fork.sh +0 -5
|
@@ -0,0 +1,1257 @@
|
|
|
1
|
+
/* ---------------------------------------------------------------------------
|
|
2
|
+
|
|
3
|
+
C-native median / percentile / quantile. Written as a C extension
|
|
4
|
+
(not a transliteration of the Ruby script): the readability goal is
|
|
5
|
+
that the compute logic reads as C, not as Ruby executed from C.
|
|
6
|
+
|
|
7
|
+
Three lanes, dispatched on data_type:
|
|
8
|
+
|
|
9
|
+
- numeric (CA_INT8..CA_FLOAT64): full C compute. The kth selection /
|
|
10
|
+
per-fiber sort already produces a fresh *contiguous* entity
|
|
11
|
+
(rb_ca_partition_copy_c, sort_copy), so all post-selection extraction
|
|
12
|
+
and arithmetic is a plain loop-interchange (OUTER/M/INNER) over
|
|
13
|
+
native-typed buffers -> double, written into an rb_ca_new_reduced
|
|
14
|
+
CA_FLOAT64 output. Inner loops are instantiated per native type
|
|
15
|
+
(X-macro, dispatched once) so the per-element read is a typed pointer
|
|
16
|
+
load, not a switch; the even-median upper-region min is fmin-based so
|
|
17
|
+
it vectorises. No Ruby-surface funcall on this lane.
|
|
18
|
+
|
|
19
|
+
- object (CA_OBJECT): arithmetic on arbitrary Ruby objects is
|
|
20
|
+
irreducibly rb_funcall, so this lane drives the same CArray-level
|
|
21
|
+
operations (partition_copy / slice / min / max / `+` / `/` / `*`) as
|
|
22
|
+
per-array funcalls. Matches production exactly: flat -> bare scalar,
|
|
23
|
+
axis -> reduced CArray, mask+axis -> partition_copy raises.
|
|
24
|
+
|
|
25
|
+
- fixlen: rejected (no numeric midpoint; ordering-pick methods alone
|
|
26
|
+
are a partial surface, so fixlen median/percentile is not offered).
|
|
27
|
+
|
|
28
|
+
Each function below is tagged with the lane it belongs to:
|
|
29
|
+
|
|
30
|
+
[numeric] - numeric lane (CA_INT8..CA_FLOAT64); pure C double math,
|
|
31
|
+
no Ruby-surface funcall.
|
|
32
|
+
[object] - CA_OBJECT lane; CArray-level / stored-object funcall.
|
|
33
|
+
[shared] - used by both lanes (or by the entry dispatch).
|
|
34
|
+
[entry] - Ruby method entry: validates args, dispatches by data_type.
|
|
35
|
+
|
|
36
|
+
NOTE the two `pct_compute*` siblings: pct_compute is [numeric] (double
|
|
37
|
+
in/out); pct_compute_object is [object] (funcall) and is reached ONLY by
|
|
38
|
+
CA_OBJECT percentile/quantile -- never by numeric, never by median.
|
|
39
|
+
|
|
40
|
+
---------------------------------------------------------------------------- */
|
|
41
|
+
|
|
42
|
+
#include "ruby.h"
|
|
43
|
+
#include "carray.h"
|
|
44
|
+
#include <math.h>
|
|
45
|
+
|
|
46
|
+
/* Externals (link-time; kept out of carray.h to keep the header lean). */
|
|
47
|
+
extern VALUE rb_ca_partition_copy_c (VALUE self, VALUE vkth, VALUE vaxis); /* carray_partition.c */
|
|
48
|
+
extern VALUE rb_ca_insert_axis (int argc, VALUE *argv, VALUE self); /* ca_obj_refer.c */
|
|
49
|
+
|
|
50
|
+
static ID id_axis, id_sort_copy, id_is_not_masked;
|
|
51
|
+
/* object lane (arbitrary Ruby objects -> arithmetic is irreducibly funcall) */
|
|
52
|
+
static ID id_plus, id_div, id_mul, id_min, id_max, id_sort, id_copy;
|
|
53
|
+
static ID id_aref, id_aset;
|
|
54
|
+
static VALUE sym_linear, sym_lower, sym_higher, sym_nearest, sym_midpoint;
|
|
55
|
+
|
|
56
|
+
/* [shared] data_type lane selector: true for CA_INT8..CA_FLOAT64. */
|
|
57
|
+
static inline int
|
|
58
|
+
mp_is_numeric (int8_t dt)
|
|
59
|
+
{
|
|
60
|
+
return ( dt >= CA_INT8 && dt <= CA_FLOAT64 );
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* [numeric] X-macro over the numeric data_types. All hot inner loops
|
|
64
|
+
are instantiated per native type (dispatched ONCE on data_type) so
|
|
65
|
+
the per-element load is a typed pointer read, not a switch: a
|
|
66
|
+
generic per-element dispatch defeats vectorisation. */
|
|
67
|
+
#define MP_TYPES(_) \
|
|
68
|
+
_(CA_INT8, int8_t) _(CA_UINT8, uint8_t) \
|
|
69
|
+
_(CA_INT16, int16_t) _(CA_UINT16, uint16_t) \
|
|
70
|
+
_(CA_INT32, int32_t) _(CA_UINT32, uint32_t) \
|
|
71
|
+
_(CA_INT64, int64_t) _(CA_UINT64, uint64_t) \
|
|
72
|
+
_(CA_FLOAT32, float32_t) _(CA_FLOAT64, double)
|
|
73
|
+
|
|
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). */
|
|
81
|
+
#define MP_GEN_MINUP(CT, T) \
|
|
82
|
+
static double \
|
|
83
|
+
mp_minup_##T (const T *b, ca_size_t f0, long k, long n, long stride) { \
|
|
84
|
+
double hi = (double) INFINITY; \
|
|
85
|
+
for ( long ai = k + 1; ai < n; ai++ ) { \
|
|
86
|
+
double v = (double) b[f0 + (ca_size_t)(ai * stride)]; \
|
|
87
|
+
hi = fmin(hi, v); \
|
|
88
|
+
} \
|
|
89
|
+
return ( hi == (double) INFINITY ) \
|
|
90
|
+
? (double) b[f0 + (ca_size_t)(k * stride)] : hi; \
|
|
91
|
+
}
|
|
92
|
+
MP_TYPES(MP_GEN_MINUP)
|
|
93
|
+
|
|
94
|
+
/* ---- geometry helper ------------------------------------------------- */
|
|
95
|
+
|
|
96
|
+
/* [numeric] split self.shape around `axis` into the contiguous-entity
|
|
97
|
+
walk dims: OUTER (axes before) x M (= dim[axis]) x INNER (axes after). */
|
|
98
|
+
static void
|
|
99
|
+
mp_geometry (CArray *ca, long axis, long *OUTER, long *M, long *INNER)
|
|
100
|
+
{
|
|
101
|
+
long inner = 1, outer = 1;
|
|
102
|
+
for ( int d = (int)axis + 1; d < ca->ndim; d++ ) inner *= ca->dim[d];
|
|
103
|
+
for ( int d = 0; d < (int)axis; d++ ) outer *= ca->dim[d];
|
|
104
|
+
*INNER = inner;
|
|
105
|
+
*OUTER = outer;
|
|
106
|
+
*M = ca->dim[axis];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* [numeric] keep_axis: [1,1,...,1] CA_FLOAT64 entity (original ndim)
|
|
110
|
+
holding a scalar (or a single masked cell when is_undef). */
|
|
111
|
+
static VALUE
|
|
112
|
+
mp_keep_axis_full_f64 (VALUE self, double v, int is_undef)
|
|
113
|
+
{
|
|
114
|
+
CArray *ca;
|
|
115
|
+
GetCArray(self, ca);
|
|
116
|
+
int nd = ca->ndim;
|
|
117
|
+
ca_size_t *dim = ALLOCA_N(ca_size_t, nd);
|
|
118
|
+
for ( int i = 0; i < nd; i++ ) dim[i] = 1;
|
|
119
|
+
VALUE out = rb_carray_new(CA_FLOAT64, (int8_t) nd, dim, 0, NULL);
|
|
120
|
+
CArray *co;
|
|
121
|
+
GetCArray(out, co);
|
|
122
|
+
if ( is_undef ) {
|
|
123
|
+
ca_create_mask(co);
|
|
124
|
+
((boolean8_t *) co->mask->ptr)[0] = 1;
|
|
125
|
+
} else {
|
|
126
|
+
((double *) co->ptr)[0] = v;
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* [shared] all-UNDEF reduced CA_FLOAT64 output for a zero-length reduction
|
|
132
|
+
axis. An order statistic of no elements has no value, so every reduced
|
|
133
|
+
cell is masked -- matching mean / min (which return UNDEF cells), not a
|
|
134
|
+
raise. keep_axis controls whether the reduced axis is dropped or kept
|
|
135
|
+
as a length-1 axis. */
|
|
136
|
+
static VALUE
|
|
137
|
+
mp_axis_all_masked (VALUE self, long axis, int keep_axis)
|
|
138
|
+
{
|
|
139
|
+
int8_t ax = (int8_t) axis;
|
|
140
|
+
VALUE out = rb_ca_new_reduced(self, &ax, 1, CA_FLOAT64, keep_axis);
|
|
141
|
+
CArray *co;
|
|
142
|
+
GetCArray(out, co);
|
|
143
|
+
ca_create_mask(co);
|
|
144
|
+
boolean8_t *m = (boolean8_t *) co->mask->ptr;
|
|
145
|
+
for ( ca_size_t i = 0; i < co->elements; i++ ) m[i] = 1;
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* ---- 5-method percentile picker -------------------------------------- */
|
|
150
|
+
|
|
151
|
+
typedef enum { PCT_LOWER_ONLY, PCT_UPPER_ONLY, PCT_BOTH } pct_need_t;
|
|
152
|
+
|
|
153
|
+
/* [shared] which of lower (sorted[k]) / upper (sorted[k+1]) the method
|
|
154
|
+
needs. Lets the caller skip the fetch it won't use. Used by both
|
|
155
|
+
lanes (the need classification is dtype-independent). */
|
|
156
|
+
static pct_need_t
|
|
157
|
+
pct_need (VALUE method, long k, double r, long n)
|
|
158
|
+
{
|
|
159
|
+
if ( method == sym_lower ) return PCT_LOWER_ONLY;
|
|
160
|
+
if ( method == sym_higher ) return (r == 0.0) ? PCT_LOWER_ONLY : PCT_UPPER_ONLY;
|
|
161
|
+
if ( method == sym_nearest ) {
|
|
162
|
+
int use_k = (r < 0.5) || (r == 0.5 && (k % 2 == 0));
|
|
163
|
+
return use_k ? PCT_LOWER_ONLY : PCT_UPPER_ONLY;
|
|
164
|
+
}
|
|
165
|
+
if ( method == sym_linear ) {
|
|
166
|
+
if ( r == 0.0 || k + 1 >= n ) return PCT_LOWER_ONLY;
|
|
167
|
+
return PCT_BOTH;
|
|
168
|
+
}
|
|
169
|
+
if ( method == sym_midpoint ) {
|
|
170
|
+
if ( k + 1 >= n ) return PCT_LOWER_ONLY;
|
|
171
|
+
return PCT_BOTH;
|
|
172
|
+
}
|
|
173
|
+
rb_raise(rb_eArgError, "percentile: invalid method (BUG)");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* [numeric] 5-method picker, double in/out. (The [object] twin is
|
|
177
|
+
pct_compute_object, far below in the object lane.) */
|
|
178
|
+
static double
|
|
179
|
+
pct_compute (VALUE method, long k, double r, long n, double lo, double hi)
|
|
180
|
+
{
|
|
181
|
+
if ( method == sym_lower ) return lo;
|
|
182
|
+
if ( method == sym_higher ) return (r == 0.0) ? lo : hi;
|
|
183
|
+
if ( method == sym_nearest ) {
|
|
184
|
+
int use_k = (r < 0.5) || (r == 0.5 && (k % 2 == 0));
|
|
185
|
+
return use_k ? lo : hi;
|
|
186
|
+
}
|
|
187
|
+
if ( method == sym_linear ) {
|
|
188
|
+
if ( r == 0.0 || k + 1 >= n ) return lo;
|
|
189
|
+
return lo * (1.0 - r) + hi * r;
|
|
190
|
+
}
|
|
191
|
+
if ( method == sym_midpoint ) {
|
|
192
|
+
if ( k + 1 >= n ) return lo;
|
|
193
|
+
return (lo + hi) / 2.0;
|
|
194
|
+
}
|
|
195
|
+
rb_raise(rb_eArgError, "percentile: invalid method (BUG)");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* [shared] reject any method symbol outside the supported 5. */
|
|
199
|
+
static void
|
|
200
|
+
pct_validate_method (VALUE method)
|
|
201
|
+
{
|
|
202
|
+
if ( method != sym_linear && method != sym_lower && method != sym_higher
|
|
203
|
+
&& method != sym_nearest && method != sym_midpoint ) {
|
|
204
|
+
rb_raise(rb_eArgError,
|
|
205
|
+
"percentile: method %"PRIsVALUE" not supported "
|
|
206
|
+
"(use :linear / :lower / :higher / :nearest / :midpoint)",
|
|
207
|
+
rb_inspect(method));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* =====================================================================
|
|
212
|
+
median -- [numeric] lane (CA_OBJECT median lives in the object lane)
|
|
213
|
+
===================================================================== */
|
|
214
|
+
|
|
215
|
+
/* [numeric] fill a reduced CA_FLOAT64 output from a partitioned entity
|
|
216
|
+
(typed; one instantiation per native type). */
|
|
217
|
+
#define MP_GEN_MEDFILL(CT, T) \
|
|
218
|
+
static void \
|
|
219
|
+
mp_medfill_##T (double *op, const T *b, long OUTER, long M, \
|
|
220
|
+
long INNER, long n, long k, int odd) { \
|
|
221
|
+
for ( long o = 0; o < OUTER; o++ ) { \
|
|
222
|
+
for ( long in = 0; in < INNER; in++ ) { \
|
|
223
|
+
ca_size_t f0 = (ca_size_t)((o * M) * INNER + in); \
|
|
224
|
+
double lo = (double) b[f0 + (ca_size_t)(k * INNER)]; \
|
|
225
|
+
op[o * INNER + in] = odd ? lo \
|
|
226
|
+
: (lo + mp_minup_##T(b, f0, k, n, INNER)) / 2.0; \
|
|
227
|
+
} \
|
|
228
|
+
} \
|
|
229
|
+
}
|
|
230
|
+
MP_TYPES(MP_GEN_MEDFILL)
|
|
231
|
+
|
|
232
|
+
/* [numeric] dispatch median_fill once on data_type to the typed body. */
|
|
233
|
+
static void
|
|
234
|
+
median_fill (double *op, int8_t dt, const char *base,
|
|
235
|
+
long OUTER, long M, long INNER, long n, long k, int odd)
|
|
236
|
+
{
|
|
237
|
+
switch ( dt ) {
|
|
238
|
+
#define MP_CASE(CT, T) \
|
|
239
|
+
case CT: mp_medfill_##T(op, (const T *) base, OUTER, M, INNER, n, k, odd); break;
|
|
240
|
+
MP_TYPES(MP_CASE)
|
|
241
|
+
#undef MP_CASE
|
|
242
|
+
default:
|
|
243
|
+
rb_raise(rb_eCADataTypeError, "median: unsupported data_type %d", dt);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* [numeric] per-axis median -> reduced CA_FLOAT64 CArray. */
|
|
248
|
+
static VALUE
|
|
249
|
+
median_axis (VALUE self, long axis, long n, int keep_axis)
|
|
250
|
+
{
|
|
251
|
+
CArray *ca;
|
|
252
|
+
GetCArray(self, ca);
|
|
253
|
+
int8_t dt = ca->data_type;
|
|
254
|
+
long k = (n % 2 == 0) ? (n / 2 - 1) : ((n - 1) / 2);
|
|
255
|
+
|
|
256
|
+
VALUE pp = rb_ca_partition_copy_c(self, LONG2NUM(k), LONG2NUM(axis));
|
|
257
|
+
CArray *cp;
|
|
258
|
+
GetCArray(pp, cp);
|
|
259
|
+
|
|
260
|
+
int8_t ax = (int8_t) axis;
|
|
261
|
+
VALUE out = rb_ca_new_reduced(self, &ax, 1, CA_FLOAT64, keep_axis);
|
|
262
|
+
CArray *co;
|
|
263
|
+
GetCArray(out, co);
|
|
264
|
+
|
|
265
|
+
long OUTER, M, INNER;
|
|
266
|
+
mp_geometry(ca, axis, &OUTER, &M, &INNER);
|
|
267
|
+
median_fill((double *) co->ptr, dt, (const char *) cp->ptr,
|
|
268
|
+
OUTER, M, INNER, n, k, (n % 2 != 0));
|
|
269
|
+
RB_GC_GUARD(pp);
|
|
270
|
+
return out;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* [numeric] flat median scalar (double) of a 1-D numeric entity/view. */
|
|
274
|
+
static double
|
|
275
|
+
median_scalar_1d (VALUE src1d, long n)
|
|
276
|
+
{
|
|
277
|
+
CArray *ca;
|
|
278
|
+
GetCArray(src1d, ca);
|
|
279
|
+
long k = (n % 2 == 0) ? (n / 2 - 1) : ((n - 1) / 2);
|
|
280
|
+
VALUE pp = rb_ca_partition_copy_c(src1d, LONG2NUM(k), INT2FIX(0));
|
|
281
|
+
CArray *cp;
|
|
282
|
+
GetCArray(pp, cp);
|
|
283
|
+
double res;
|
|
284
|
+
median_fill(&res, ca->data_type, (const char *) cp->ptr,
|
|
285
|
+
1, n, 1, n, k, (n % 2 != 0)); /* OUTER=1, M=n, INNER=1 */
|
|
286
|
+
RB_GC_GUARD(pp);
|
|
287
|
+
return res;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* [numeric] flat median: strip mask / honour min_count, flatten, then
|
|
291
|
+
reduce on axis 0 to a bare Float (or keep_axis [1..1] entity). */
|
|
292
|
+
static VALUE
|
|
293
|
+
median_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
|
|
294
|
+
{
|
|
295
|
+
VALUE src = self;
|
|
296
|
+
int filled = 0; /* masked-out or empty -> use fill_value / UNDEF */
|
|
297
|
+
|
|
298
|
+
if ( RTEST(rb_ca_has_mask(self)) ) {
|
|
299
|
+
CArray *mc; GetCArray(self, mc);
|
|
300
|
+
long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
|
|
301
|
+
if ( cnt < min_count ) filled = 1;
|
|
302
|
+
else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
|
|
303
|
+
}
|
|
304
|
+
if ( !filled ) {
|
|
305
|
+
CArray *sc;
|
|
306
|
+
GetCArray(src, sc);
|
|
307
|
+
if ( sc->elements == 0 ) filled = 1;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if ( filled ) {
|
|
311
|
+
int is_undef = NIL_P(fill_value);
|
|
312
|
+
double fillv = is_undef ? 0.0 : NUM2DBL(fill_value);
|
|
313
|
+
if ( keep_axis ) return mp_keep_axis_full_f64(self, fillv, is_undef);
|
|
314
|
+
return is_undef ? CA_UNDEF : fill_value;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
VALUE flat = rb_ca_flatten(src);
|
|
318
|
+
CArray *fc;
|
|
319
|
+
GetCArray(flat, fc);
|
|
320
|
+
double scalar = median_scalar_1d(flat, (long) fc->elements);
|
|
321
|
+
RB_GC_GUARD(flat);
|
|
322
|
+
if ( keep_axis ) return mp_keep_axis_full_f64(self, scalar, 0);
|
|
323
|
+
return rb_float_new(scalar);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* =====================================================================
|
|
327
|
+
object lane (CA_OBJECT)
|
|
328
|
+
|
|
329
|
+
Arithmetic on arbitrary Ruby objects is irreducibly rb_funcall, so
|
|
330
|
+
this lane drives the *same* CArray-level operations the numeric lane
|
|
331
|
+
expresses inline in C: partition_copy / slice / min / max / `+` / `/`
|
|
332
|
+
/ `*`. Combines are per-array funcalls (not per-scalar loops). This
|
|
333
|
+
matches production behaviour exactly: flat -> bare scalar, axis ->
|
|
334
|
+
reduced CArray, mask+axis -> partition_copy raises.
|
|
335
|
+
|
|
336
|
+
Every function in this block (down to the median entry) is [object].
|
|
337
|
+
===================================================================== */
|
|
338
|
+
|
|
339
|
+
/* [object] recv.mid(axis: raxis) -- e.g. min/max/sort along an axis. */
|
|
340
|
+
static VALUE
|
|
341
|
+
obj_call_axis (VALUE recv, ID mid, VALUE raxis)
|
|
342
|
+
{
|
|
343
|
+
VALUE kw = rb_hash_new();
|
|
344
|
+
rb_hash_aset(kw, ID2SYM(id_axis), raxis);
|
|
345
|
+
VALUE argv[1] = { kw };
|
|
346
|
+
return rb_funcallv_kw(recv, mid, 1, argv, RB_PASS_KEYWORDS);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* [object] arr[*spec] with spec[axis]=idx, rest nil (= drop one axis). */
|
|
350
|
+
static VALUE
|
|
351
|
+
obj_slice (VALUE arr, long axis, long idx)
|
|
352
|
+
{
|
|
353
|
+
CArray *ca;
|
|
354
|
+
GetCArray(arr, ca);
|
|
355
|
+
int ndim = ca->ndim;
|
|
356
|
+
VALUE *spec = ALLOCA_N(VALUE, ndim);
|
|
357
|
+
for ( int i = 0; i < ndim; i++ ) spec[i] = Qnil;
|
|
358
|
+
spec[axis] = LONG2NUM(idx);
|
|
359
|
+
return rb_funcallv(arr, id_aref, ndim, spec);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/* [object] min over pp[*spec] with spec[axis] = (k+1)..(n-1) along axis. */
|
|
363
|
+
static VALUE
|
|
364
|
+
obj_min_upper (VALUE pp, long axis, long k, long n)
|
|
365
|
+
{
|
|
366
|
+
CArray *ca;
|
|
367
|
+
GetCArray(pp, ca);
|
|
368
|
+
int ndim = ca->ndim;
|
|
369
|
+
VALUE *spec = ALLOCA_N(VALUE, ndim);
|
|
370
|
+
for ( int i = 0; i < ndim; i++ ) spec[i] = Qnil;
|
|
371
|
+
spec[axis] = rb_range_new(LONG2NUM(k + 1), LONG2NUM(n - 1), 0);
|
|
372
|
+
VALUE sliced = rb_funcallv(pp, id_aref, ndim, spec);
|
|
373
|
+
return obj_call_axis(sliced, id_min, LONG2NUM(axis));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* [object] sorted[k] along axis via partition_copy. */
|
|
377
|
+
static VALUE
|
|
378
|
+
obj_kth_one (VALUE self, long axis, long k)
|
|
379
|
+
{
|
|
380
|
+
VALUE pp = rb_ca_partition_copy_c(self, LONG2NUM(k), LONG2NUM(axis));
|
|
381
|
+
return obj_slice(pp, axis, k);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* [object] sorted[k] and sorted[k+1] from a single partition_copy(k). */
|
|
385
|
+
static void
|
|
386
|
+
obj_kth_pair (VALUE self, long axis, long k, long n, VALUE *lo, VALUE *hi)
|
|
387
|
+
{
|
|
388
|
+
VALUE pp = rb_ca_partition_copy_c(self, LONG2NUM(k), LONG2NUM(axis));
|
|
389
|
+
*lo = obj_slice(pp, axis, k);
|
|
390
|
+
*hi = obj_min_upper(pp, axis, k, n);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/* [object] keep_axis flat: [1,...,1] entity holding val (UNDEF assigns
|
|
394
|
+
masked). Uses []= so UNDEF / arbitrary objects store correctly. */
|
|
395
|
+
static VALUE
|
|
396
|
+
obj_keep_axis_full (VALUE self, VALUE val)
|
|
397
|
+
{
|
|
398
|
+
CArray *ca;
|
|
399
|
+
GetCArray(self, ca);
|
|
400
|
+
int ndim = ca->ndim;
|
|
401
|
+
ca_size_t *dim = ALLOCA_N(ca_size_t, ndim);
|
|
402
|
+
for ( int i = 0; i < ndim; i++ ) dim[i] = 1;
|
|
403
|
+
int8_t ot = (ca->data_type == CA_OBJECT) ? CA_OBJECT : CA_FLOAT64;
|
|
404
|
+
VALUE out = rb_carray_new(ot, (int8_t) ndim, dim, 0, NULL);
|
|
405
|
+
VALUE *av = ALLOCA_N(VALUE, ndim + 1);
|
|
406
|
+
for ( int i = 0; i < ndim; i++ ) av[i] = INT2FIX(0);
|
|
407
|
+
av[ndim] = val;
|
|
408
|
+
rb_funcallv(out, id_aset, ndim + 1, av);
|
|
409
|
+
return out;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* [object] per-axis median (= reduced CArray; 1-D self -> bare scalar).
|
|
413
|
+
even-n combine and the *1.0 float-promote are stored-object funcalls. */
|
|
414
|
+
static VALUE
|
|
415
|
+
median_object_axis (VALUE self, long axis, long n, int keep_axis)
|
|
416
|
+
{
|
|
417
|
+
VALUE result;
|
|
418
|
+
if ( n % 2 == 0 ) {
|
|
419
|
+
long k = n / 2 - 1;
|
|
420
|
+
VALUE lo, hi;
|
|
421
|
+
obj_kth_pair(self, axis, k, n, &lo, &hi);
|
|
422
|
+
result = rb_funcall(rb_funcall(lo, id_plus, 1, hi), id_div, 1, DBL2NUM(2.0));
|
|
423
|
+
} else {
|
|
424
|
+
VALUE kv = obj_kth_one(self, axis, (n - 1) / 2);
|
|
425
|
+
result = rb_funcall(kv, id_mul, 1, DBL2NUM(1.0)); /* force float promote */
|
|
426
|
+
}
|
|
427
|
+
if ( keep_axis ) {
|
|
428
|
+
VALUE ia[1] = { LONG2NUM(axis) };
|
|
429
|
+
result = rb_ca_insert_axis(1, ia, result);
|
|
430
|
+
}
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* [object] flat median: mask-strip / min_count, flatten, axis-0 reduce. */
|
|
435
|
+
static VALUE
|
|
436
|
+
median_object_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
|
|
437
|
+
{
|
|
438
|
+
VALUE src = self, result;
|
|
439
|
+
int filled = 0;
|
|
440
|
+
if ( RTEST(rb_ca_has_mask(self)) ) {
|
|
441
|
+
CArray *mc; GetCArray(self, mc);
|
|
442
|
+
long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
|
|
443
|
+
if ( cnt < min_count ) filled = 1;
|
|
444
|
+
else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
|
|
445
|
+
}
|
|
446
|
+
if ( !filled ) {
|
|
447
|
+
CArray *sc;
|
|
448
|
+
GetCArray(src, sc);
|
|
449
|
+
if ( sc->elements == 0 ) filled = 1;
|
|
450
|
+
}
|
|
451
|
+
if ( filled ) {
|
|
452
|
+
result = NIL_P(fill_value) ? CA_UNDEF : fill_value;
|
|
453
|
+
} else {
|
|
454
|
+
VALUE flat = rb_ca_flatten(src);
|
|
455
|
+
CArray *fc;
|
|
456
|
+
GetCArray(flat, fc);
|
|
457
|
+
result = median_object_axis(flat, 0, (long) fc->elements, 0);
|
|
458
|
+
RB_GC_GUARD(flat);
|
|
459
|
+
}
|
|
460
|
+
return keep_axis ? obj_keep_axis_full(self, result) : result;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* [object] 5-method picker on stored objects (funcall arithmetic; lo/hi
|
|
464
|
+
pre-fetched). Reached ONLY by CA_OBJECT percentile/quantile -- never
|
|
465
|
+
by numeric (that path uses pct_compute) and never by median.
|
|
466
|
+
The :linear / :midpoint interpolation funcalls are irreducible for
|
|
467
|
+
arbitrary objects; the *1.0 in the pick methods is float-promotion to
|
|
468
|
+
match the numeric "always Float" output contract. */
|
|
469
|
+
static VALUE
|
|
470
|
+
pct_compute_object (VALUE method, long k, double r, long n, VALUE lo, VALUE hi)
|
|
471
|
+
{
|
|
472
|
+
if ( method == sym_lower ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
|
|
473
|
+
if ( method == sym_higher ) {
|
|
474
|
+
VALUE v = (r == 0.0) ? lo : hi;
|
|
475
|
+
return rb_funcall(v, id_mul, 1, DBL2NUM(1.0));
|
|
476
|
+
}
|
|
477
|
+
if ( method == sym_nearest ) {
|
|
478
|
+
int use_k = (r < 0.5) || (r == 0.5 && (k % 2 == 0));
|
|
479
|
+
return rb_funcall(use_k ? lo : hi, id_mul, 1, DBL2NUM(1.0));
|
|
480
|
+
}
|
|
481
|
+
if ( method == sym_linear ) {
|
|
482
|
+
if ( r == 0.0 || k + 1 >= n ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
|
|
483
|
+
VALUE a = rb_funcall(lo, id_mul, 1, DBL2NUM(1.0 - r));
|
|
484
|
+
VALUE b = rb_funcall(hi, id_mul, 1, DBL2NUM(r));
|
|
485
|
+
return rb_funcall(a, id_plus, 1, b);
|
|
486
|
+
}
|
|
487
|
+
if ( method == sym_midpoint ) {
|
|
488
|
+
if ( k + 1 >= n ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
|
|
489
|
+
return rb_funcall(rb_funcall(lo, id_plus, 1, hi), id_div, 1, DBL2NUM(2.0));
|
|
490
|
+
}
|
|
491
|
+
rb_raise(rb_eArgError, "percentile: invalid method (BUG)");
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* [object] single p via one partition_copy. */
|
|
495
|
+
static VALUE
|
|
496
|
+
pct_object_one_partition (VALUE self, long axis, long n, double p, VALUE method)
|
|
497
|
+
{
|
|
498
|
+
if ( p == 100.0 )
|
|
499
|
+
return rb_funcall(obj_call_axis(self, id_max, LONG2NUM(axis)),
|
|
500
|
+
id_mul, 1, DBL2NUM(1.0));
|
|
501
|
+
if ( n == 1 )
|
|
502
|
+
return rb_funcall(obj_slice(self, axis, 0), id_mul, 1, DBL2NUM(1.0));
|
|
503
|
+
double f = (n - 1) * p / 100.0;
|
|
504
|
+
long k = (long) floor(f);
|
|
505
|
+
double r = f - k;
|
|
506
|
+
pct_need_t need = pct_need(method, k, r, n);
|
|
507
|
+
VALUE lo = Qnil, hi = Qnil;
|
|
508
|
+
if ( need == PCT_LOWER_ONLY ) lo = obj_kth_one(self, axis, k);
|
|
509
|
+
else obj_kth_pair(self, axis, k, n, &lo, &hi);
|
|
510
|
+
return pct_compute_object(method, k, r, n, lo, hi);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/* [object] single p from a fully sorted entity (multi-p shared sort). */
|
|
514
|
+
static VALUE
|
|
515
|
+
pct_object_one_sorted (VALUE sorted, long axis, long n, double p, VALUE method)
|
|
516
|
+
{
|
|
517
|
+
if ( p == 100.0 )
|
|
518
|
+
return rb_funcall(obj_slice(sorted, axis, n - 1), id_mul, 1, DBL2NUM(1.0));
|
|
519
|
+
if ( n == 1 )
|
|
520
|
+
return rb_funcall(obj_slice(sorted, axis, 0), id_mul, 1, DBL2NUM(1.0));
|
|
521
|
+
double f = (n - 1) * p / 100.0;
|
|
522
|
+
long k = (long) floor(f);
|
|
523
|
+
double r = f - k;
|
|
524
|
+
pct_need_t need = pct_need(method, k, r, n);
|
|
525
|
+
long kup = (k + 1 < n) ? (k + 1) : (n - 1);
|
|
526
|
+
VALUE lo = Qnil, hi = Qnil;
|
|
527
|
+
if ( need != PCT_UPPER_ONLY ) lo = obj_slice(sorted, axis, k);
|
|
528
|
+
if ( need != PCT_LOWER_ONLY ) hi = obj_slice(sorted, axis, kup);
|
|
529
|
+
return pct_compute_object(method, k, r, n, lo, hi);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* [object] per-axis percentile -> array of (reduced CArray | scalar),
|
|
533
|
+
one entry per requested p (keep_axis wrapping done by the entry). */
|
|
534
|
+
static VALUE
|
|
535
|
+
percentile_object_axis (VALUE self, VALUE pers, long axis, VALUE method)
|
|
536
|
+
{
|
|
537
|
+
CArray *ca;
|
|
538
|
+
GetCArray(self, ca);
|
|
539
|
+
long n = (long) ca->dim[axis];
|
|
540
|
+
long npers = RARRAY_LEN(pers);
|
|
541
|
+
|
|
542
|
+
if ( n == 0 ) {
|
|
543
|
+
/* zero-length axis: all-UNDEF reduced cell per p. keep_axis wrapping
|
|
544
|
+
is applied by the entry (rb_ca_percentile_m), so build reduced. */
|
|
545
|
+
VALUE res = rb_ary_new_capa(npers);
|
|
546
|
+
for ( long i = 0; i < npers; i++ )
|
|
547
|
+
rb_ary_push(res, mp_axis_all_masked(self, axis, 0));
|
|
548
|
+
return res;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if ( npers == 1 ) {
|
|
552
|
+
double p = NUM2DBL(rb_ary_entry(pers, 0));
|
|
553
|
+
return rb_ary_new_from_args(1,
|
|
554
|
+
pct_object_one_partition(self, axis, n, p, method));
|
|
555
|
+
}
|
|
556
|
+
/* multi-p: one shared sort (CA_OBJECT sort trampolines to the focused
|
|
557
|
+
Ruby per-slab helper), materialised once, then C-driven slicing. */
|
|
558
|
+
VALUE sorted = rb_funcall(obj_call_axis(self, id_sort, LONG2NUM(axis)), id_copy, 0);
|
|
559
|
+
VALUE result = rb_ary_new_capa(npers);
|
|
560
|
+
for ( long i = 0; i < npers; i++ ) {
|
|
561
|
+
double p = NUM2DBL(rb_ary_entry(pers, i));
|
|
562
|
+
rb_ary_push(result, pct_object_one_sorted(sorted, axis, n, p, method));
|
|
563
|
+
}
|
|
564
|
+
RB_GC_GUARD(sorted);
|
|
565
|
+
return result;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/* [object] masked per-axis percentile, one p. `sorted` is a materialised
|
|
569
|
+
sort(axis:) entity (present objects front, UNDEF tail per fiber); the
|
|
570
|
+
stored VALUEs are read straight from its buffer, so the per-fiber select
|
|
571
|
+
is C control flow driving only the interpolation funcalls. n_present ==
|
|
572
|
+
0 (or < min_count) yields an UNDEF cell (fill_value when given); the mask
|
|
573
|
+
is created lazily, matching the numeric lane. */
|
|
574
|
+
static VALUE
|
|
575
|
+
pct_object_axis_masked_one (VALUE self, VALUE sorted, long axis, double p,
|
|
576
|
+
VALUE method, long OUTER, long M, long INNER,
|
|
577
|
+
const boolean8_t *sm, long min_count,
|
|
578
|
+
VALUE fill_value, int keep_axis)
|
|
579
|
+
{
|
|
580
|
+
int8_t ax = (int8_t) axis;
|
|
581
|
+
VALUE out = rb_ca_new_reduced(self, &ax, 1, CA_OBJECT, keep_axis);
|
|
582
|
+
CArray *co;
|
|
583
|
+
GetCArray(out, co);
|
|
584
|
+
CArray *cs;
|
|
585
|
+
GetCArray(sorted, cs);
|
|
586
|
+
VALUE *sb = (VALUE *) cs->ptr;
|
|
587
|
+
VALUE *op = (VALUE *) co->ptr;
|
|
588
|
+
int is_undef = NIL_P(fill_value);
|
|
589
|
+
|
|
590
|
+
for ( long o = 0; o < OUTER; o++ ) {
|
|
591
|
+
for ( long in = 0; in < INNER; in++ ) {
|
|
592
|
+
ca_size_t f0 = (ca_size_t)((o * M) * INNER + in);
|
|
593
|
+
long oc = o * INNER + in;
|
|
594
|
+
long np = M;
|
|
595
|
+
if ( sm ) {
|
|
596
|
+
np = 0;
|
|
597
|
+
for ( long j = 0; j < M; j++ )
|
|
598
|
+
if ( !sm[f0 + (ca_size_t)(j * INNER)] ) np++;
|
|
599
|
+
}
|
|
600
|
+
if ( np == 0 || np < min_count ) {
|
|
601
|
+
if ( is_undef ) {
|
|
602
|
+
if ( !co->mask ) ca_create_mask(co);
|
|
603
|
+
((boolean8_t *) co->mask->ptr)[oc] = 1;
|
|
604
|
+
} else {
|
|
605
|
+
op[oc] = fill_value;
|
|
606
|
+
}
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
long k; double r;
|
|
610
|
+
if ( p == 100.0 ) { k = np - 1; r = 0.0; }
|
|
611
|
+
else if ( np == 1 ) { k = 0; r = 0.0; }
|
|
612
|
+
else { double f = (np - 1) * p / 100.0; k = (long) floor(f); r = f - k; }
|
|
613
|
+
pct_need_t need = pct_need(method, k, r, np);
|
|
614
|
+
long kup = (k + 1 < np) ? (k + 1) : (np - 1);
|
|
615
|
+
VALUE lo = Qnil, hi = Qnil;
|
|
616
|
+
if ( need != PCT_UPPER_ONLY ) lo = sb[f0 + (ca_size_t)(k * INNER)];
|
|
617
|
+
if ( need != PCT_LOWER_ONLY ) hi = sb[f0 + (ca_size_t)(kup * INNER)];
|
|
618
|
+
op[oc] = pct_compute_object(method, k, r, np, lo, hi);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return out;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/* [object] masked per-axis percentile: one sort(axis:) supplies the sorted
|
|
625
|
+
material, then each p is a per-fiber select. Returns an Array of reduced
|
|
626
|
+
CA_OBJECT CArrays, one per p. */
|
|
627
|
+
static VALUE
|
|
628
|
+
percentile_object_axis_masked (VALUE self, VALUE pers, long axis, VALUE method,
|
|
629
|
+
long min_count, VALUE fill_value, int keep_axis)
|
|
630
|
+
{
|
|
631
|
+
CArray *ca;
|
|
632
|
+
GetCArray(self, ca);
|
|
633
|
+
long npers = RARRAY_LEN(pers);
|
|
634
|
+
long M = (long) ca->dim[axis];
|
|
635
|
+
|
|
636
|
+
if ( M == 0 ) {
|
|
637
|
+
VALUE res = rb_ary_new_capa(npers);
|
|
638
|
+
for ( long i = 0; i < npers; i++ )
|
|
639
|
+
rb_ary_push(res, mp_axis_all_masked(self, axis, keep_axis));
|
|
640
|
+
return res;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
VALUE sorted = rb_funcall(obj_call_axis(self, id_sort, LONG2NUM(axis)), id_copy, 0);
|
|
644
|
+
CArray *cs;
|
|
645
|
+
GetCArray(sorted, cs);
|
|
646
|
+
const boolean8_t *sm = cs->mask ? (const boolean8_t *) cs->mask->ptr : NULL;
|
|
647
|
+
|
|
648
|
+
long OUTER, MM, INNER;
|
|
649
|
+
mp_geometry(ca, axis, &OUTER, &MM, &INNER);
|
|
650
|
+
|
|
651
|
+
VALUE result = rb_ary_new_capa(npers);
|
|
652
|
+
for ( long i = 0; i < npers; i++ ) {
|
|
653
|
+
double p = NUM2DBL(rb_ary_entry(pers, i));
|
|
654
|
+
rb_ary_push(result,
|
|
655
|
+
pct_object_axis_masked_one(self, sorted, axis, p, method,
|
|
656
|
+
OUTER, MM, INNER, sm, min_count,
|
|
657
|
+
fill_value, keep_axis));
|
|
658
|
+
}
|
|
659
|
+
RB_GC_GUARD(sorted);
|
|
660
|
+
return result;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/* [object] per-axis median with mask / min_count / fill_value (= object
|
|
664
|
+
percentile(50, :linear); see the numeric median_axis_masked note). */
|
|
665
|
+
static VALUE
|
|
666
|
+
median_object_axis_masked (VALUE self, long axis, long min_count,
|
|
667
|
+
VALUE fill_value, int keep_axis)
|
|
668
|
+
{
|
|
669
|
+
VALUE pers = rb_ary_new_from_args(1, DBL2NUM(50.0));
|
|
670
|
+
VALUE res = percentile_object_axis_masked(self, pers, axis, sym_linear,
|
|
671
|
+
min_count, fill_value, keep_axis);
|
|
672
|
+
return rb_ary_entry(res, 0);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/* [object] flat percentile: mask-strip / min_count, flatten, axis-0. */
|
|
676
|
+
static VALUE
|
|
677
|
+
percentile_object_flat (VALUE self, VALUE pers, long min_count, VALUE fill_value,
|
|
678
|
+
VALUE method)
|
|
679
|
+
{
|
|
680
|
+
long npers = RARRAY_LEN(pers);
|
|
681
|
+
VALUE src = self;
|
|
682
|
+
int filled = 0;
|
|
683
|
+
if ( RTEST(rb_ca_has_mask(self)) ) {
|
|
684
|
+
CArray *mc; GetCArray(self, mc);
|
|
685
|
+
long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
|
|
686
|
+
if ( cnt < min_count ) filled = 1;
|
|
687
|
+
else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
|
|
688
|
+
}
|
|
689
|
+
if ( !filled ) {
|
|
690
|
+
CArray *sc;
|
|
691
|
+
GetCArray(src, sc);
|
|
692
|
+
if ( sc->elements == 0 ) filled = 1;
|
|
693
|
+
}
|
|
694
|
+
if ( filled ) {
|
|
695
|
+
VALUE fill = NIL_P(fill_value) ? CA_UNDEF : fill_value;
|
|
696
|
+
VALUE result = rb_ary_new_capa(npers);
|
|
697
|
+
for ( long i = 0; i < npers; i++ ) rb_ary_push(result, fill);
|
|
698
|
+
return result;
|
|
699
|
+
}
|
|
700
|
+
VALUE flat = rb_ca_flatten(src);
|
|
701
|
+
VALUE r = percentile_object_axis(flat, pers, 0, method);
|
|
702
|
+
RB_GC_GUARD(flat);
|
|
703
|
+
return r;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/* numeric masked per-axis driver (defined in the percentile lane below). */
|
|
707
|
+
static VALUE median_axis_masked (VALUE self, long axis, long min_count,
|
|
708
|
+
VALUE fill_value, int keep_axis);
|
|
709
|
+
|
|
710
|
+
/* [entry] median(axis:, min_count:, fill_value:, keep_axis:). Dispatches
|
|
711
|
+
fixlen -> reject, object -> object lane, numeric -> numeric lane. */
|
|
712
|
+
static VALUE
|
|
713
|
+
rb_ca_median_m (int argc, VALUE *argv, VALUE self)
|
|
714
|
+
{
|
|
715
|
+
VALUE ropt = rb_pop_options(&argc, &argv);
|
|
716
|
+
VALUE raxis = Qnil, rmin_count = INT2FIX(0), fill_value = Qnil, rkeep = Qfalse;
|
|
717
|
+
rb_scan_options(ropt, "axis,min_count,fill_value,keep_axis",
|
|
718
|
+
&raxis, &rmin_count, &fill_value, &rkeep);
|
|
719
|
+
if ( argc != 0 )
|
|
720
|
+
rb_raise(rb_eArgError, "median: no positional args accepted (got %d)", argc);
|
|
721
|
+
|
|
722
|
+
CArray *ca;
|
|
723
|
+
GetCArray(self, ca);
|
|
724
|
+
/* Boolean rides the f64 lane: 0/1 -> 0.0/1.0, so median interpolates and
|
|
725
|
+
returns a float (a 2-element median averages to 0.5), matching how the
|
|
726
|
+
numeric lane treats integer input. */
|
|
727
|
+
if ( ca->data_type == CA_BOOLEAN ) {
|
|
728
|
+
self = rb_ca_wrap_readonly(self, INT2NUM(CA_FLOAT64));
|
|
729
|
+
GetCArray(self, ca);
|
|
730
|
+
}
|
|
731
|
+
int8_t dt = ca->data_type;
|
|
732
|
+
if ( ca_is_fixlen_type(ca) )
|
|
733
|
+
rb_raise(rb_eCADataTypeError,
|
|
734
|
+
"median: not defined for fixlen (no numeric midpoint); "
|
|
735
|
+
"use a numeric or object array");
|
|
736
|
+
int is_obj = (dt == CA_OBJECT);
|
|
737
|
+
if ( !is_obj && !mp_is_numeric(dt) )
|
|
738
|
+
rb_raise(rb_eCADataTypeError, "median: unsupported data_type %d", dt);
|
|
739
|
+
|
|
740
|
+
long min_count = NUM2LONG(rmin_count);
|
|
741
|
+
if ( min_count < 0 )
|
|
742
|
+
rb_raise(rb_eArgError, "min_count must be non-negative; got %ld", min_count);
|
|
743
|
+
int keep_axis = RTEST(rkeep);
|
|
744
|
+
|
|
745
|
+
if ( !NIL_P(raxis) ) {
|
|
746
|
+
long axis = rb_ca_normalize_axis_value(self, raxis, "median");
|
|
747
|
+
long n = (long) ca->dim[axis];
|
|
748
|
+
if ( n == 0 ) return mp_axis_all_masked(self, axis, keep_axis);
|
|
749
|
+
/* mask / min_count / fill_value require the per-fiber select (each fiber
|
|
750
|
+
has its own n_present); the plain partition path assumes a uniform n. */
|
|
751
|
+
if ( RTEST(rb_ca_has_mask(self)) || min_count > 0 || !NIL_P(fill_value) )
|
|
752
|
+
return is_obj
|
|
753
|
+
? median_object_axis_masked(self, axis, min_count, fill_value, keep_axis)
|
|
754
|
+
: median_axis_masked(self, axis, min_count, fill_value, keep_axis);
|
|
755
|
+
return is_obj ? median_object_axis(self, axis, n, keep_axis)
|
|
756
|
+
: median_axis(self, axis, n, keep_axis);
|
|
757
|
+
}
|
|
758
|
+
return is_obj ? median_object_flat(self, min_count, fill_value, keep_axis)
|
|
759
|
+
: median_flat(self, min_count, fill_value, keep_axis);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/* =====================================================================
|
|
763
|
+
percentile / quantile -- [numeric] lane below; the [object] percentile
|
|
764
|
+
functions live up in the object lane. The entries (rb_ca_percentile_m
|
|
765
|
+
/ rb_ca_quantile_m) at the bottom dispatch between them.
|
|
766
|
+
===================================================================== */
|
|
767
|
+
|
|
768
|
+
/* [numeric] fill a reduced CA_FLOAT64 output for one p, from either a
|
|
769
|
+
partitioned entity (from_sorted == 0; upper via mp_minup) or a fully
|
|
770
|
+
sorted entity (from_sorted == 1; upper = base[kup]). */
|
|
771
|
+
#define MP_GEN_PCTFILL(CT, T) \
|
|
772
|
+
static void \
|
|
773
|
+
mp_pctfill_##T (double *op, const T *b, int from_sorted, \
|
|
774
|
+
long OUTER, long M, long INNER, long n, \
|
|
775
|
+
long k, double r, VALUE method, pct_need_t need) { \
|
|
776
|
+
long kup = (k + 1 < n) ? (k + 1) : (n - 1); \
|
|
777
|
+
for ( long o = 0; o < OUTER; o++ ) { \
|
|
778
|
+
for ( long in = 0; in < INNER; in++ ) { \
|
|
779
|
+
ca_size_t f0 = (ca_size_t)((o * M) * INNER + in); \
|
|
780
|
+
double lo = 0.0, hi = 0.0; \
|
|
781
|
+
if ( need != PCT_UPPER_ONLY ) \
|
|
782
|
+
lo = (double) b[f0 + (ca_size_t)(k * INNER)]; \
|
|
783
|
+
if ( need != PCT_LOWER_ONLY ) \
|
|
784
|
+
hi = from_sorted ? (double) b[f0 + (ca_size_t)(kup * INNER)] \
|
|
785
|
+
: mp_minup_##T(b, f0, k, n, INNER); \
|
|
786
|
+
if ( need == PCT_UPPER_ONLY ) lo = hi; \
|
|
787
|
+
op[o * INNER + in] = pct_compute(method, k, r, n, lo, hi); \
|
|
788
|
+
} \
|
|
789
|
+
} \
|
|
790
|
+
}
|
|
791
|
+
MP_TYPES(MP_GEN_PCTFILL)
|
|
792
|
+
|
|
793
|
+
/* [numeric] dispatch pct_fill once on data_type to the typed body. */
|
|
794
|
+
static void
|
|
795
|
+
pct_fill (double *op, int8_t dt, const char *base, int from_sorted,
|
|
796
|
+
long OUTER, long M, long INNER, long n,
|
|
797
|
+
long k, double r, VALUE method, pct_need_t need)
|
|
798
|
+
{
|
|
799
|
+
switch ( dt ) {
|
|
800
|
+
#define MP_CASE(CT, T) \
|
|
801
|
+
case CT: mp_pctfill_##T(op, (const T *) base, from_sorted, \
|
|
802
|
+
OUTER, M, INNER, n, k, r, method, need); break;
|
|
803
|
+
MP_TYPES(MP_CASE)
|
|
804
|
+
#undef MP_CASE
|
|
805
|
+
default:
|
|
806
|
+
rb_raise(rb_eCADataTypeError, "percentile: unsupported data_type %d", dt);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/* [numeric] masked per-axis percentile fill. `b` / `sm` are the sorted-copy
|
|
811
|
+
entity's data and mask (present values front, UNDEF tail per fiber -- the
|
|
812
|
+
invariant sort_copy(axis:) guarantees for masked input), so n_present per
|
|
813
|
+
fiber = the count of non-masked cells (its leading run). A fiber with
|
|
814
|
+
n_present == 0 (or < min_count) yields an UNDEF cell, or fill_value when
|
|
815
|
+
given. The output mask is created lazily on the first UNDEF cell so a run
|
|
816
|
+
with no empty fiber keeps has_mask == false, matching mean(axis:). */
|
|
817
|
+
#define MP_GEN_PCTFILL_MASKED(CT, T) \
|
|
818
|
+
static void \
|
|
819
|
+
mp_pctfill_masked_##T (CArray *co, const T *b, const boolean8_t *sm, \
|
|
820
|
+
long OUTER, long M, long INNER, double p, \
|
|
821
|
+
VALUE method, long min_count, \
|
|
822
|
+
int is_undef, double fillv) { \
|
|
823
|
+
double *op = (double *) co->ptr; \
|
|
824
|
+
for ( long o = 0; o < OUTER; o++ ) { \
|
|
825
|
+
for ( long in = 0; in < INNER; in++ ) { \
|
|
826
|
+
ca_size_t f0 = (ca_size_t)((o * M) * INNER + in); \
|
|
827
|
+
long oc = o * INNER + in; \
|
|
828
|
+
long np = M; \
|
|
829
|
+
if ( sm ) { \
|
|
830
|
+
np = 0; \
|
|
831
|
+
for ( long j = 0; j < M; j++ ) \
|
|
832
|
+
if ( !sm[f0 + (ca_size_t)(j * INNER)] ) np++; \
|
|
833
|
+
} \
|
|
834
|
+
if ( np == 0 || np < min_count ) { \
|
|
835
|
+
if ( is_undef ) { \
|
|
836
|
+
if ( !co->mask ) ca_create_mask(co); \
|
|
837
|
+
((boolean8_t *) co->mask->ptr)[oc] = 1; \
|
|
838
|
+
op[oc] = 0.0; \
|
|
839
|
+
} else { \
|
|
840
|
+
op[oc] = fillv; \
|
|
841
|
+
} \
|
|
842
|
+
continue; \
|
|
843
|
+
} \
|
|
844
|
+
long k; double r; \
|
|
845
|
+
if ( p == 100.0 ) { k = np - 1; r = 0.0; } \
|
|
846
|
+
else if ( np == 1 ) { k = 0; r = 0.0; } \
|
|
847
|
+
else { double f = (np - 1) * p / 100.0; k = (long) floor(f); r = f - k; } \
|
|
848
|
+
pct_need_t need = pct_need(method, k, r, np); \
|
|
849
|
+
long kup = (k + 1 < np) ? (k + 1) : (np - 1); \
|
|
850
|
+
double lo = 0.0, hi = 0.0; \
|
|
851
|
+
if ( need != PCT_UPPER_ONLY ) \
|
|
852
|
+
lo = (double) b[f0 + (ca_size_t)(k * INNER)]; \
|
|
853
|
+
if ( need != PCT_LOWER_ONLY ) \
|
|
854
|
+
hi = (double) b[f0 + (ca_size_t)(kup * INNER)]; \
|
|
855
|
+
if ( need == PCT_UPPER_ONLY ) lo = hi; \
|
|
856
|
+
op[oc] = pct_compute(method, k, r, np, lo, hi); \
|
|
857
|
+
} \
|
|
858
|
+
} \
|
|
859
|
+
}
|
|
860
|
+
MP_TYPES(MP_GEN_PCTFILL_MASKED)
|
|
861
|
+
|
|
862
|
+
/* [numeric] dispatch the masked fill once on data_type to the typed body. */
|
|
863
|
+
static void
|
|
864
|
+
pct_fill_masked (CArray *co, int8_t dt, const char *base, const boolean8_t *sm,
|
|
865
|
+
long OUTER, long M, long INNER, double p, VALUE method,
|
|
866
|
+
long min_count, int is_undef, double fillv)
|
|
867
|
+
{
|
|
868
|
+
switch ( dt ) {
|
|
869
|
+
#define MP_CASE(CT, T) \
|
|
870
|
+
case CT: mp_pctfill_masked_##T(co, (const T *) base, sm, OUTER, M, INNER, \
|
|
871
|
+
p, method, min_count, is_undef, fillv); break;
|
|
872
|
+
MP_TYPES(MP_CASE)
|
|
873
|
+
#undef MP_CASE
|
|
874
|
+
default:
|
|
875
|
+
rb_raise(rb_eCADataTypeError, "percentile: unsupported data_type %d", dt);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/* [numeric] single p via one partition_copy -> reduced CA_FLOAT64. */
|
|
880
|
+
static VALUE
|
|
881
|
+
pct_axis_one_partition (VALUE self, long axis, long n, double p,
|
|
882
|
+
VALUE method, int keep_axis)
|
|
883
|
+
{
|
|
884
|
+
CArray *ca;
|
|
885
|
+
GetCArray(self, ca);
|
|
886
|
+
int8_t dt = ca->data_type;
|
|
887
|
+
|
|
888
|
+
long k;
|
|
889
|
+
double r;
|
|
890
|
+
if ( p == 100.0 ) { k = n - 1; r = 0.0; }
|
|
891
|
+
else if ( n == 1 ) { k = 0; r = 0.0; }
|
|
892
|
+
else { double f = (n - 1) * p / 100.0; k = (long) floor(f); r = f - k; }
|
|
893
|
+
|
|
894
|
+
pct_need_t need = pct_need(method, k, r, n);
|
|
895
|
+
/* For higher/nearest that pick the upper element, we still partition at
|
|
896
|
+
k and read min-upper; but UPPER_ONLY means we need element k+1. To
|
|
897
|
+
keep one partition correct, partition at the index we actually read:
|
|
898
|
+
if UPPER_ONLY we need sorted[k+1] -> partition at k, min-upper gives
|
|
899
|
+
it. pct_fill handles that via from_sorted==0. */
|
|
900
|
+
VALUE pp = rb_ca_partition_copy_c(self, LONG2NUM(k), LONG2NUM(axis));
|
|
901
|
+
CArray *cp;
|
|
902
|
+
GetCArray(pp, cp);
|
|
903
|
+
|
|
904
|
+
int8_t ax = (int8_t) axis;
|
|
905
|
+
VALUE out = rb_ca_new_reduced(self, &ax, 1, CA_FLOAT64, keep_axis);
|
|
906
|
+
CArray *co;
|
|
907
|
+
GetCArray(out, co);
|
|
908
|
+
long OUTER, M, INNER;
|
|
909
|
+
mp_geometry(ca, axis, &OUTER, &M, &INNER);
|
|
910
|
+
pct_fill((double *) co->ptr, dt, (const char *) cp->ptr, 0,
|
|
911
|
+
OUTER, M, INNER, n, k, r, method, need);
|
|
912
|
+
RB_GC_GUARD(pp);
|
|
913
|
+
return out;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/* [numeric] multi p: one shared sort_copy + per-p C slice. (The single
|
|
917
|
+
sort_copy funcall is the only Ruby-surface call left on this lane; it
|
|
918
|
+
could be replaced by a C-callable sort_copy twin like partition_copy_c.) */
|
|
919
|
+
static VALUE
|
|
920
|
+
pct_axis_multi (VALUE self, VALUE pers_ary, long axis, long n,
|
|
921
|
+
VALUE method, int keep_axis)
|
|
922
|
+
{
|
|
923
|
+
CArray *ca;
|
|
924
|
+
GetCArray(self, ca);
|
|
925
|
+
int8_t dt = ca->data_type;
|
|
926
|
+
long npers = RARRAY_LEN(pers_ary);
|
|
927
|
+
|
|
928
|
+
/* one funcall to trigger the C per-fiber sort; result is a fresh
|
|
929
|
+
contiguous entity. Picking is full-C below. */
|
|
930
|
+
VALUE kw = rb_hash_new();
|
|
931
|
+
rb_hash_aset(kw, ID2SYM(id_axis), LONG2NUM(axis));
|
|
932
|
+
VALUE sc_argv[1] = { kw };
|
|
933
|
+
VALUE sorted = rb_funcallv_kw(self, id_sort_copy, 1, sc_argv, RB_PASS_KEYWORDS);
|
|
934
|
+
CArray *cs;
|
|
935
|
+
GetCArray(sorted, cs);
|
|
936
|
+
|
|
937
|
+
long OUTER, M, INNER;
|
|
938
|
+
mp_geometry(ca, axis, &OUTER, &M, &INNER);
|
|
939
|
+
|
|
940
|
+
VALUE result = rb_ary_new_capa(npers);
|
|
941
|
+
for ( long i = 0; i < npers; i++ ) {
|
|
942
|
+
double p = NUM2DBL(rb_ary_entry(pers_ary, i));
|
|
943
|
+
long k;
|
|
944
|
+
double r;
|
|
945
|
+
if ( p == 100.0 ) { k = n - 1; r = 0.0; }
|
|
946
|
+
else if ( n == 1 ) { k = 0; r = 0.0; }
|
|
947
|
+
else { double f = (n - 1) * p / 100.0; k = (long) floor(f); r = f - k; }
|
|
948
|
+
pct_need_t need = pct_need(method, k, r, n);
|
|
949
|
+
|
|
950
|
+
int8_t ax = (int8_t) axis;
|
|
951
|
+
VALUE out = rb_ca_new_reduced(self, &ax, 1, CA_FLOAT64, keep_axis);
|
|
952
|
+
CArray *co;
|
|
953
|
+
GetCArray(out, co);
|
|
954
|
+
pct_fill((double *) co->ptr, dt, (const char *) cs->ptr, 1,
|
|
955
|
+
OUTER, M, INNER, n, k, r, method, need);
|
|
956
|
+
rb_ary_push(result, out);
|
|
957
|
+
}
|
|
958
|
+
RB_GC_GUARD(sorted);
|
|
959
|
+
return result;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/* [numeric] per-axis percentile: 1 p -> partition, many p -> shared sort. */
|
|
963
|
+
static VALUE
|
|
964
|
+
pct_axis (VALUE self, VALUE pers_ary, long axis, VALUE method, int keep_axis)
|
|
965
|
+
{
|
|
966
|
+
CArray *ca;
|
|
967
|
+
GetCArray(self, ca);
|
|
968
|
+
long n = (long) ca->dim[axis];
|
|
969
|
+
long npers = RARRAY_LEN(pers_ary);
|
|
970
|
+
|
|
971
|
+
if ( n == 0 ) {
|
|
972
|
+
/* zero-length axis: every requested p reduces to an all-UNDEF cell. */
|
|
973
|
+
VALUE res = rb_ary_new_capa(npers);
|
|
974
|
+
for ( long i = 0; i < npers; i++ )
|
|
975
|
+
rb_ary_push(res, mp_axis_all_masked(self, axis, keep_axis));
|
|
976
|
+
return res;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if ( npers == 1 ) {
|
|
980
|
+
double p = NUM2DBL(rb_ary_entry(pers_ary, 0));
|
|
981
|
+
return rb_ary_new_from_args(1,
|
|
982
|
+
pct_axis_one_partition(self, axis, n, p, method, keep_axis));
|
|
983
|
+
}
|
|
984
|
+
return pct_axis_multi(self, pers_ary, axis, n, method, keep_axis);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/* [numeric] per-axis percentile with mask / min_count / fill_value: a single
|
|
988
|
+
masked sort_copy (present front, UNDEF tail per fiber) supplies the sorted
|
|
989
|
+
material, then each p is a per-fiber select honouring that fiber's own
|
|
990
|
+
n_present. Returns an Array of reduced CA_FLOAT64 CArrays, one per p. */
|
|
991
|
+
static VALUE
|
|
992
|
+
pct_axis_masked (VALUE self, VALUE pers_ary, long axis, VALUE method,
|
|
993
|
+
long min_count, VALUE fill_value, int keep_axis)
|
|
994
|
+
{
|
|
995
|
+
CArray *ca;
|
|
996
|
+
GetCArray(self, ca);
|
|
997
|
+
int8_t dt = ca->data_type;
|
|
998
|
+
long npers = RARRAY_LEN(pers_ary);
|
|
999
|
+
long M = (long) ca->dim[axis];
|
|
1000
|
+
|
|
1001
|
+
if ( M == 0 ) {
|
|
1002
|
+
VALUE res = rb_ary_new_capa(npers);
|
|
1003
|
+
for ( long i = 0; i < npers; i++ )
|
|
1004
|
+
rb_ary_push(res, mp_axis_all_masked(self, axis, keep_axis));
|
|
1005
|
+
return res;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/* one masked sort_copy: present values front, UNDEF tail, per fiber. */
|
|
1009
|
+
VALUE kw = rb_hash_new();
|
|
1010
|
+
rb_hash_aset(kw, ID2SYM(id_axis), LONG2NUM(axis));
|
|
1011
|
+
VALUE sc_argv[1] = { kw };
|
|
1012
|
+
VALUE sorted = rb_funcallv_kw(self, id_sort_copy, 1, sc_argv, RB_PASS_KEYWORDS);
|
|
1013
|
+
CArray *cs;
|
|
1014
|
+
GetCArray(sorted, cs);
|
|
1015
|
+
const boolean8_t *sm = cs->mask ? (const boolean8_t *) cs->mask->ptr : NULL;
|
|
1016
|
+
|
|
1017
|
+
long OUTER, MM, INNER;
|
|
1018
|
+
mp_geometry(ca, axis, &OUTER, &MM, &INNER);
|
|
1019
|
+
int is_undef = NIL_P(fill_value);
|
|
1020
|
+
double fillv = is_undef ? 0.0 : NUM2DBL(fill_value);
|
|
1021
|
+
|
|
1022
|
+
VALUE result = rb_ary_new_capa(npers);
|
|
1023
|
+
for ( long i = 0; i < npers; i++ ) {
|
|
1024
|
+
double p = NUM2DBL(rb_ary_entry(pers_ary, i));
|
|
1025
|
+
int8_t ax = (int8_t) axis;
|
|
1026
|
+
VALUE out = rb_ca_new_reduced(self, &ax, 1, CA_FLOAT64, keep_axis);
|
|
1027
|
+
CArray *co;
|
|
1028
|
+
GetCArray(out, co);
|
|
1029
|
+
pct_fill_masked(co, dt, (const char *) cs->ptr, sm,
|
|
1030
|
+
OUTER, MM, INNER, p, method, min_count, is_undef, fillv);
|
|
1031
|
+
rb_ary_push(result, out);
|
|
1032
|
+
}
|
|
1033
|
+
RB_GC_GUARD(sorted);
|
|
1034
|
+
return result;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/* [numeric] per-axis median with mask / min_count / fill_value. Median is
|
|
1038
|
+
percentile(50, :linear) (odd n -> middle, even n -> mean of the two middle
|
|
1039
|
+
values), so it rides the same per-fiber select. */
|
|
1040
|
+
static VALUE
|
|
1041
|
+
median_axis_masked (VALUE self, long axis, long min_count, VALUE fill_value,
|
|
1042
|
+
int keep_axis)
|
|
1043
|
+
{
|
|
1044
|
+
VALUE pers = rb_ary_new_from_args(1, DBL2NUM(50.0));
|
|
1045
|
+
VALUE res = pct_axis_masked(self, pers, axis, sym_linear,
|
|
1046
|
+
min_count, fill_value, keep_axis);
|
|
1047
|
+
return rb_ary_entry(res, 0);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/* [numeric] flat percentile -> array of scalars (or keep_axis [1..1]
|
|
1051
|
+
entities), one per requested p. */
|
|
1052
|
+
static VALUE
|
|
1053
|
+
pct_flat (VALUE self, VALUE pers_ary, long min_count, VALUE fill_value,
|
|
1054
|
+
VALUE method, int keep_axis)
|
|
1055
|
+
{
|
|
1056
|
+
long npers = RARRAY_LEN(pers_ary);
|
|
1057
|
+
VALUE src = self;
|
|
1058
|
+
int masked_out = 0, is_undef = 0;
|
|
1059
|
+
double fillv = 0.0;
|
|
1060
|
+
|
|
1061
|
+
if ( RTEST(rb_ca_has_mask(self)) ) {
|
|
1062
|
+
CArray *mc; GetCArray(self, mc);
|
|
1063
|
+
long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
|
|
1064
|
+
if ( cnt < min_count ) {
|
|
1065
|
+
masked_out = 1; is_undef = NIL_P(fill_value);
|
|
1066
|
+
if ( !is_undef ) fillv = NUM2DBL(fill_value);
|
|
1067
|
+
} else {
|
|
1068
|
+
src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if ( !masked_out ) {
|
|
1072
|
+
CArray *sc;
|
|
1073
|
+
GetCArray(src, sc);
|
|
1074
|
+
if ( sc->elements == 0 ) {
|
|
1075
|
+
masked_out = 1; is_undef = NIL_P(fill_value);
|
|
1076
|
+
if ( !is_undef ) fillv = NUM2DBL(fill_value);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
VALUE result = rb_ary_new_capa(npers);
|
|
1081
|
+
|
|
1082
|
+
if ( masked_out ) {
|
|
1083
|
+
for ( long i = 0; i < npers; i++ ) {
|
|
1084
|
+
VALUE v = keep_axis ? mp_keep_axis_full_f64(self, fillv, is_undef)
|
|
1085
|
+
: (is_undef ? CA_UNDEF
|
|
1086
|
+
: (NIL_P(fill_value) ? CA_UNDEF : fill_value));
|
|
1087
|
+
rb_ary_push(result, v);
|
|
1088
|
+
}
|
|
1089
|
+
return result;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/* flatten -> 1-D, reduce on axis 0, extract op[0] per p. */
|
|
1093
|
+
VALUE flat = rb_ca_flatten(src);
|
|
1094
|
+
VALUE per_axis = pct_axis(flat, pers_ary, 0, method, 0); /* array of [1] CArrays */
|
|
1095
|
+
for ( long i = 0; i < npers; i++ ) {
|
|
1096
|
+
CArray *r1;
|
|
1097
|
+
GetCArray(rb_ary_entry(per_axis, i), r1);
|
|
1098
|
+
double v = ((double *) r1->ptr)[0];
|
|
1099
|
+
VALUE out = keep_axis ? mp_keep_axis_full_f64(self, v, 0) : rb_float_new(v);
|
|
1100
|
+
rb_ary_push(result, out);
|
|
1101
|
+
}
|
|
1102
|
+
RB_GC_GUARD(flat);
|
|
1103
|
+
return result;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/* [shared] normalise + validate the p list: unwrap a single Array/CArray
|
|
1107
|
+
arg, reject empty, require each p be Numeric in [0,100]. */
|
|
1108
|
+
static VALUE
|
|
1109
|
+
pct_flatten_validate_pers (VALUE pers)
|
|
1110
|
+
{
|
|
1111
|
+
if ( RARRAY_LEN(pers) == 1 ) {
|
|
1112
|
+
VALUE first = rb_ary_entry(pers, 0);
|
|
1113
|
+
if ( RB_TYPE_P(first, T_ARRAY) ) pers = first;
|
|
1114
|
+
else if ( rb_obj_is_kind_of(first, rb_cCArray) )
|
|
1115
|
+
pers = rb_funcall(first, rb_intern("to_a"), 0);
|
|
1116
|
+
}
|
|
1117
|
+
long len = RARRAY_LEN(pers);
|
|
1118
|
+
if ( len == 0 )
|
|
1119
|
+
rb_raise(rb_eArgError, "percentile: at least one p value required");
|
|
1120
|
+
for ( long i = 0; i < len; i++ ) {
|
|
1121
|
+
VALUE p = rb_ary_entry(pers, i);
|
|
1122
|
+
if ( !rb_obj_is_kind_of(p, rb_cNumeric) || NUM2DBL(p) < 0.0 || NUM2DBL(p) > 100.0 )
|
|
1123
|
+
rb_raise(rb_eArgError,
|
|
1124
|
+
"percentile: p must be Numeric in [0,100] (got %"PRIsVALUE")",
|
|
1125
|
+
rb_inspect(p));
|
|
1126
|
+
}
|
|
1127
|
+
return pers;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/* [entry] percentile(*pers, axis:, min_count:, fill_value:, method:,
|
|
1131
|
+
keep_axis:). Dispatches fixlen -> reject, object -> object lane,
|
|
1132
|
+
numeric -> numeric lane. */
|
|
1133
|
+
static VALUE
|
|
1134
|
+
rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
|
|
1135
|
+
{
|
|
1136
|
+
VALUE ropt = rb_pop_options(&argc, &argv);
|
|
1137
|
+
VALUE raxis = Qnil, rmin_count = INT2FIX(0), fill_value = Qnil,
|
|
1138
|
+
method = sym_linear, rkeep = Qfalse;
|
|
1139
|
+
rb_scan_options(ropt, "axis,min_count,fill_value,method,keep_axis",
|
|
1140
|
+
&raxis, &rmin_count, &fill_value, &method, &rkeep);
|
|
1141
|
+
|
|
1142
|
+
CArray *ca;
|
|
1143
|
+
GetCArray(self, ca);
|
|
1144
|
+
/* Boolean rides the f64 lane (0/1 -> 0.0/1.0), so percentile / quantile
|
|
1145
|
+
interpolate and return a float, matching the integer lane. */
|
|
1146
|
+
if ( ca->data_type == CA_BOOLEAN ) {
|
|
1147
|
+
self = rb_ca_wrap_readonly(self, INT2NUM(CA_FLOAT64));
|
|
1148
|
+
GetCArray(self, ca);
|
|
1149
|
+
}
|
|
1150
|
+
int8_t dt = ca->data_type;
|
|
1151
|
+
if ( ca_is_fixlen_type(ca) )
|
|
1152
|
+
rb_raise(rb_eCADataTypeError,
|
|
1153
|
+
"percentile: not defined for fixlen; use a numeric or object array");
|
|
1154
|
+
int is_obj = (dt == CA_OBJECT);
|
|
1155
|
+
if ( !is_obj && !mp_is_numeric(dt) )
|
|
1156
|
+
rb_raise(rb_eCADataTypeError, "percentile: unsupported data_type %d", dt);
|
|
1157
|
+
|
|
1158
|
+
long min_count = NUM2LONG(rmin_count);
|
|
1159
|
+
if ( min_count < 0 )
|
|
1160
|
+
rb_raise(rb_eArgError, "min_count must be non-negative; got %ld", min_count);
|
|
1161
|
+
|
|
1162
|
+
VALUE pers = pct_flatten_validate_pers(rb_ary_new_from_values(argc, argv));
|
|
1163
|
+
pct_validate_method(method);
|
|
1164
|
+
int keep_axis = RTEST(rkeep);
|
|
1165
|
+
int single_p = (RARRAY_LEN(pers) == 1);
|
|
1166
|
+
|
|
1167
|
+
/* numeric lane bakes keep_axis into the reduced output; object lane
|
|
1168
|
+
returns unwrapped results and is keep-wrapped here (insert_axis for
|
|
1169
|
+
the per-axis form, obj_keep_axis_full for the flat form) -- matching
|
|
1170
|
+
production's structure for arbitrary-object results.
|
|
1171
|
+
|
|
1172
|
+
Return shape: multi-p returns Array<Float> (flat) or Array<CArray>
|
|
1173
|
+
(per-axis); single-p unwraps the length-1 Array so the caller gets
|
|
1174
|
+
Float / CArray directly. A single Array or CArray p argument is
|
|
1175
|
+
flattened first and follows the same rule (length 1 unwraps). */
|
|
1176
|
+
VALUE result;
|
|
1177
|
+
if ( !NIL_P(raxis) ) {
|
|
1178
|
+
long axis = rb_ca_normalize_axis_value(self, raxis, "percentile");
|
|
1179
|
+
/* mask / min_count / fill_value require the per-fiber select (each fiber
|
|
1180
|
+
has its own n_present); the plain partition/sort path assumes a
|
|
1181
|
+
uniform n. These paths bake keep_axis into the reduced output. */
|
|
1182
|
+
int per_fiber = RTEST(rb_ca_has_mask(self)) || min_count > 0
|
|
1183
|
+
|| !NIL_P(fill_value);
|
|
1184
|
+
if ( !is_obj ) {
|
|
1185
|
+
result = per_fiber
|
|
1186
|
+
? pct_axis_masked(self, pers, axis, method, min_count, fill_value, keep_axis)
|
|
1187
|
+
: pct_axis(self, pers, axis, method, keep_axis);
|
|
1188
|
+
} else if ( per_fiber ) {
|
|
1189
|
+
result = percentile_object_axis_masked(self, pers, axis, method,
|
|
1190
|
+
min_count, fill_value, keep_axis);
|
|
1191
|
+
} else {
|
|
1192
|
+
result = percentile_object_axis(self, pers, axis, method);
|
|
1193
|
+
if ( keep_axis ) {
|
|
1194
|
+
long len = RARRAY_LEN(result);
|
|
1195
|
+
VALUE w = rb_ary_new_capa(len);
|
|
1196
|
+
VALUE ia[1] = { LONG2NUM(axis) };
|
|
1197
|
+
for ( long i = 0; i < len; i++ )
|
|
1198
|
+
rb_ary_push(w, rb_ca_insert_axis(1, ia, rb_ary_entry(result, i)));
|
|
1199
|
+
result = w;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
} else if ( !is_obj ) {
|
|
1203
|
+
result = pct_flat(self, pers, min_count, fill_value, method, keep_axis);
|
|
1204
|
+
} else {
|
|
1205
|
+
result = percentile_object_flat(self, pers, min_count, fill_value, method);
|
|
1206
|
+
if ( keep_axis ) {
|
|
1207
|
+
long len = RARRAY_LEN(result);
|
|
1208
|
+
VALUE w = rb_ary_new_capa(len);
|
|
1209
|
+
for ( long i = 0; i < len; i++ )
|
|
1210
|
+
rb_ary_push(w, obj_keep_axis_full(self, rb_ary_entry(result, i)));
|
|
1211
|
+
result = w;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if ( single_p ) return rb_ary_entry(result, 0);
|
|
1216
|
+
return result;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/* [entry] quantile(axis:, keep_axis:) = percentile(0, 25, 50, 75, 100, ...).
|
|
1220
|
+
Return shape: Array<Float> len 5 (flat) or Array<CArray> len 5 (per-axis),
|
|
1221
|
+
matching percentile's multi-p wrapping since we pass 5 fixed p values. */
|
|
1222
|
+
static VALUE
|
|
1223
|
+
rb_ca_quantile_m (int argc, VALUE *argv, VALUE self)
|
|
1224
|
+
{
|
|
1225
|
+
VALUE ropt = rb_pop_options(&argc, &argv);
|
|
1226
|
+
if ( argc != 0 )
|
|
1227
|
+
rb_raise(rb_eArgError, "quantile: no positional args accepted (got %d)", argc);
|
|
1228
|
+
VALUE raxis = Qnil, rkeep = Qfalse;
|
|
1229
|
+
rb_scan_options(ropt, "axis,keep_axis", &raxis, &rkeep);
|
|
1230
|
+
VALUE opts = rb_hash_new();
|
|
1231
|
+
if ( !NIL_P(raxis) ) rb_hash_aset(opts, ID2SYM(rb_intern("axis")), raxis);
|
|
1232
|
+
rb_hash_aset(opts, ID2SYM(rb_intern("keep_axis")), rkeep);
|
|
1233
|
+
VALUE pa[6] = { INT2FIX(0), INT2FIX(25), INT2FIX(50), INT2FIX(75), INT2FIX(100),
|
|
1234
|
+
opts };
|
|
1235
|
+
return rb_ca_percentile_m(6, pa, self);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
void
|
|
1239
|
+
Init_carray_median_percentile (void)
|
|
1240
|
+
{
|
|
1241
|
+
id_axis = rb_intern("axis");
|
|
1242
|
+
id_sort_copy = rb_intern("sort_copy");
|
|
1243
|
+
id_is_not_masked = rb_intern("is_not_masked");
|
|
1244
|
+
id_plus = rb_intern("+"); id_div = rb_intern("/"); id_mul = rb_intern("*");
|
|
1245
|
+
id_min = rb_intern("min"); id_max = rb_intern("max");
|
|
1246
|
+
id_sort = rb_intern("sort"); id_copy = rb_intern("copy");
|
|
1247
|
+
id_aref = rb_intern("[]"); id_aset = rb_intern("[]=");
|
|
1248
|
+
sym_linear = ID2SYM(rb_intern("linear"));
|
|
1249
|
+
sym_lower = ID2SYM(rb_intern("lower"));
|
|
1250
|
+
sym_higher = ID2SYM(rb_intern("higher"));
|
|
1251
|
+
sym_nearest = ID2SYM(rb_intern("nearest"));
|
|
1252
|
+
sym_midpoint = ID2SYM(rb_intern("midpoint"));
|
|
1253
|
+
|
|
1254
|
+
rb_define_method(rb_cCArray, "median", rb_ca_median_m, -1);
|
|
1255
|
+
rb_define_method(rb_cCArray, "percentile", rb_ca_percentile_m, -1);
|
|
1256
|
+
rb_define_method(rb_cCArray, "quantile", rb_ca_quantile_m, -1);
|
|
1257
|
+
}
|