carray 3.0.3 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +264 -0
- data/README.md +3 -2
- data/carray.gemspec +1 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +1 -6
- data/ext/ca_axis_group.c +27 -27
- data/ext/ca_binop_dispatch.c +1 -29
- data/ext/ca_binop_dispatch.h +0 -1
- data/ext/ca_categorical_iterator.c +205 -134
- data/ext/ca_compare.c +7 -18
- data/ext/ca_group_iter.c +16 -17
- data/ext/ca_kernel_iterator.c +1234 -1994
- data/ext/ca_kernel_iterator.h +391 -437
- data/ext/ca_monop_dispatch.c +82 -59
- data/ext/ca_monop_dispatch.h +1 -0
- data/ext/ca_obj_array.c +96 -39
- data/ext/ca_obj_bincmp.c +24 -21
- data/ext/ca_obj_binop.c +15 -26
- data/ext/ca_obj_bitarray.c +3 -2
- data/ext/ca_obj_bitfield.c +5 -6
- data/ext/ca_obj_block.c +18 -9
- data/ext/ca_obj_byte_swap.c +5 -6
- data/ext/ca_obj_const_string.c +173 -19
- data/ext/ca_obj_face.c +238 -57
- data/ext/ca_obj_face.h +19 -0
- data/ext/ca_obj_fake.c +15 -29
- data/ext/ca_obj_farray.c +6 -3
- data/ext/ca_obj_field.c +17 -5
- data/ext/ca_obj_fixlen_string.c +2 -3
- data/ext/ca_obj_grid.c +406 -132
- data/ext/ca_obj_meld.c +172 -34
- data/ext/ca_obj_moncmp.c +23 -8
- data/ext/ca_obj_monop.c +67 -57
- data/ext/ca_obj_object.c +157 -51
- data/ext/ca_obj_record.c +12 -7
- data/ext/ca_obj_reduce.c +13 -8
- data/ext/ca_obj_refer.c +25 -27
- data/ext/ca_obj_remap.c +30 -9
- data/ext/ca_obj_repeat.c +9 -10
- data/ext/ca_obj_roll.c +6 -5
- data/ext/ca_obj_select.c +14 -9
- data/ext/ca_obj_select_axis.c +15 -47
- data/ext/ca_obj_shift.c +11 -1
- data/ext/ca_obj_stack.c +179 -34
- data/ext/ca_obj_stride.c +162 -115
- data/ext/ca_obj_string.c +71 -4
- data/ext/ca_obj_tile.c +6 -5
- data/ext/ca_obj_time.c +22 -6
- data/ext/ca_obj_timedelta.c +22 -6
- data/ext/ca_obj_transpose.c +5 -3
- data/ext/ca_obj_triop.c +23 -32
- data/ext/ca_obj_window.c +190 -26
- data/ext/ca_sweep_engine.c +2 -2
- data/ext/caframe_csv_reader.c +80 -1
- data/ext/carray.h +181 -75
- data/ext/carray_access.c +184 -96
- data/ext/carray_address_basis.c +343 -87
- data/ext/carray_attribute.c +89 -6
- data/ext/carray_bincount.c +10 -8
- data/ext/carray_broadcast.c +13 -8
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +108 -0
- data/ext/carray_cast.c +142 -24
- data/ext/carray_cast_func.rb +46 -6
- data/ext/carray_class.c +4 -3
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +16 -7
- data/ext/carray_copy.c +51 -14
- data/ext/carray_core.c +147 -55
- data/ext/carray_count.c +50 -20
- data/ext/carray_element.c +76 -33
- data/ext/carray_factorize.c +118 -64
- data/ext/carray_generate.c +191 -50
- data/ext/carray_histogram.c +62 -17
- data/ext/carray_hold.c +12 -15
- data/ext/carray_index_classifier.c +388 -176
- data/ext/carray_internal.h +336 -29
- data/ext/carray_kernels_bincmp.c +4453 -0
- data/ext/carray_kernels_binop.c +11368 -0
- data/ext/carray_kernels_init.c +1734 -0
- data/ext/carray_kernels_moncmp.c +2104 -0
- data/ext/carray_kernels_monop.c +18369 -0
- data/ext/carray_kernels_reduce_aggregate.c +23818 -0
- data/ext/carray_kernels_reduce_boolean.c +406 -0
- data/ext/carray_kernels_reduce_cumulative.c +10086 -0
- data/ext/carray_kernels_reduce_extreme.c +17634 -0
- data/ext/carray_kernels_reduce_variance.c +3849 -0
- data/ext/carray_kernels_scan.c +4302 -0
- data/ext/carray_kernels_search.c +33829 -0
- data/ext/carray_kernels_sort.c +10906 -0
- data/ext/carray_kernels_triop.c +1864 -0
- data/ext/carray_key_windows.c +216 -0
- data/ext/carray_lazy.c +170 -111
- data/ext/carray_mask.c +292 -72
- data/ext/carray_median_percentile.c +82 -28
- data/ext/carray_memory_view.c +669 -561
- data/ext/carray_operator.c +124 -47
- data/ext/carray_order.c +384 -76
- data/ext/carray_partition.c +28 -27
- data/ext/carray_random.c +67 -13
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +63 -38
- data/ext/carray_segment.c +3 -3
- data/ext/carray_slab.c +91 -28
- data/ext/carray_sort.c +35 -35
- data/ext/carray_struct.c +19 -18
- data/ext/carray_test.c +26 -5
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +246 -45
- data/ext/mk_call_cfunc.rb +19 -2
- data/ext/mkkernel.rb +198 -240
- data/ext/ruby_carray.c +10 -4
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +126 -22
- data/lib/carray/autoload_carray.rb +8 -31
- data/lib/carray/autoload_method_extension.rb +14 -5
- data/lib/carray/axis_group.rb +255 -106
- data/lib/carray/basics.rb +78 -10
- data/lib/carray/bincount_nd.rb +12 -2
- data/lib/carray/block_iterator.rb +126 -23
- data/lib/carray/boolean_reduce.rb +17 -17
- data/lib/carray/categorical.rb +165 -43
- data/lib/carray/categorical_iterator.rb +130 -27
- data/lib/carray/complex.rb +5 -5
- data/lib/carray/conditional.rb +67 -18
- data/lib/carray/const_string.rb +14 -5
- data/lib/carray/construct.rb +15 -11
- data/lib/carray/data_type_extension.rb +1 -0
- data/lib/carray/distance_limit.rb +77 -0
- data/lib/carray/fixlen_string.rb +7 -1
- data/lib/carray/frame/column_verbs.rb +70 -0
- data/lib/carray/frame/convert.rb +69 -0
- data/lib/carray/frame/csv_parser.rb +111 -17
- data/lib/carray/frame/describe.rb +3 -2
- data/lib/carray/frame/frame.rb +246 -21
- data/lib/carray/frame/group.rb +510 -95
- data/lib/carray/frame/io.rb +115 -28
- data/lib/carray/frame/join.rb +312 -51
- data/lib/carray/frame/records.rb +83 -7
- data/lib/carray/frame/reshape.rb +42 -35
- data/lib/carray/frame/sort.rb +7 -19
- data/lib/carray/frame/verbs.rb +188 -48
- data/lib/carray/frame/windows.rb +117 -0
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +11 -4
- data/lib/carray/fusion.rb +40 -16
- data/lib/carray/histogram.rb +6 -1
- data/lib/carray/inspect.rb +31 -8
- data/lib/carray/iterator.rb +85 -7
- data/lib/carray/key_windows.rb +620 -0
- data/lib/carray/lazy.rb +182 -183
- data/lib/carray/mask_gap_fill.rb +63 -57
- data/lib/carray/meld_reduce.rb +12 -14
- data/lib/carray/methods/bin.rb +26 -18
- data/lib/carray/methods/bincount.rb +16 -0
- data/lib/carray/methods/bit_string.rb +1 -1
- data/lib/carray/methods/choose.rb +16 -3
- data/lib/carray/methods/composition.rb +3 -1
- data/lib/carray/methods/gather_nd.rb +14 -5
- data/lib/carray/methods/insert_block.rb +2 -2
- data/lib/carray/methods/is_between.rb +59 -0
- data/lib/carray/methods/is_in.rb +53 -36
- data/lib/carray/methods/join.rb +1 -6
- data/lib/carray/methods/lag.rb +49 -0
- data/lib/carray/methods/locate_addr.rb +47 -51
- data/lib/carray/methods/mode.rb +1 -1
- data/lib/carray/methods/nunique.rb +11 -6
- data/lib/carray/methods/resize.rb +3 -3
- data/lib/carray/methods/value_counts.rb +4 -11
- data/lib/carray/record_order.rb +3 -3
- data/lib/carray/rng.rb +3 -0
- data/lib/carray/runtime.rb +53 -8
- data/lib/carray/segment_iterator.rb +313 -82
- data/lib/carray/serialize.rb +24 -10
- data/lib/carray/slab_iterator.rb +50 -0
- data/lib/carray/string.rb +105 -5
- data/lib/carray/string_operation_extension.rb +8 -1
- data/lib/carray/struct.rb +9 -21
- data/lib/carray/struct_builder.rb +19 -8
- data/lib/carray/time.rb +726 -104
- data/lib/carray/window_iterator.rb +122 -16
- data/lib/carray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +4 -0
- data/yard-stubs/ca_obj_grid.rb +21 -10
- data/yard-stubs/ca_obj_refer.rb +2 -1
- data/yard-stubs/ca_obj_roll.rb +2 -2
- data/yard-stubs/ca_obj_shift.rb +5 -1
- data/yard-stubs/ca_obj_tile.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +8 -3
- data/yard-stubs/carray_access.rb +6 -1
- data/yard-stubs/carray_attribute.rb +0 -5
- data/yard-stubs/carray_cast.rb +10 -0
- data/yard-stubs/carray_conversion.rb +2 -1
- data/yard-stubs/carray_copy.rb +4 -2
- data/yard-stubs/carray_count.rb +4 -4
- data/yard-stubs/carray_element.rb +10 -4
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_mask.rb +26 -2
- data/yard-stubs/carray_math.rb +27 -1
- data/yard-stubs/carray_median_percentile.rb +5 -2
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +29 -18
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +14 -9
- data/yard-stubs/carray_slab.rb +9 -2
- data/yard-stubs/carray_sort.rb +6 -5
- data/yard-stubs/carray_test.rb +5 -3
- metadata +25 -3
- data/lib/carray/arrow_tensor.rb +0 -401
- data/lib/carray/table.rb +0 -108
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
# ----------------------------------------------------------------------------
|
|
2
|
+
#
|
|
3
|
+
# carray/key_windows.rb
|
|
4
|
+
#
|
|
5
|
+
# CAKeyWindowIterator: a moving window bounded by a width of a key (a time
|
|
6
|
+
# or a number) rather than by a count of cells. Built by
|
|
7
|
+
# `value.windows(on: key, before:, after:)`.
|
|
8
|
+
#
|
|
9
|
+
# ----------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
require "carray/segment_iterator"
|
|
12
|
+
require "carray/distance_limit"
|
|
13
|
+
|
|
14
|
+
# A moving window per row, bounded by a width of a key: the window of a row
|
|
15
|
+
# whose key is t holds the rows whose key lies between t - before and
|
|
16
|
+
# t + after. Each reduction answers once per row, in row order.
|
|
17
|
+
#
|
|
18
|
+
# Given `at:`, the windows sit at those points instead of at the rows: the
|
|
19
|
+
# window of a point t holds the rows whose key lies between t - before and
|
|
20
|
+
# t + after, and each reduction answers once per point, in the order of the
|
|
21
|
+
# points. A grid of points gives windows on a grid (`after:` the width,
|
|
22
|
+
# `exclude: :upper` for [t, t + width)); a window with no row answers as an
|
|
23
|
+
# empty reduction does.
|
|
24
|
+
#
|
|
25
|
+
# Besides the reductions, the iterator answers what each window covered:
|
|
26
|
+
# {#first_addr} / {#last_addr} (the rows), {#first_key} / {#last_key} (their
|
|
27
|
+
# keys), {#elements} (the number of rows) and {#bounds} (the range the
|
|
28
|
+
# window was asked to cover), and, given `interval:`, {#is_complete}
|
|
29
|
+
# (whether the window holds exactly the keys of that grid).
|
|
30
|
+
#
|
|
31
|
+
# The keys need not be sorted. A row whose key (or `by:` key) is masked, or
|
|
32
|
+
# a float key that is NaN or infinite, is in no window and has no window: its
|
|
33
|
+
# answers are UNDEF (a distance to an infinite key has no value). A masked
|
|
34
|
+
# value is in the windows (it counts in {#elements}) but, as in any
|
|
35
|
+
# reduction, takes no part in the reductions.
|
|
36
|
+
#
|
|
37
|
+
# The windows overlap, so the per-cell operations of the partition members
|
|
38
|
+
# (`map`, the scans, `lag`, `order`, `strip_mask`) are not offered.
|
|
39
|
+
class CAKeyWindowIterator < CAIterator
|
|
40
|
+
|
|
41
|
+
EXCLUDE = [nil, :lower, :upper, :both].freeze
|
|
42
|
+
private_constant :EXCLUDE
|
|
43
|
+
|
|
44
|
+
# value : the 1-D CArray whose windows are reduced.
|
|
45
|
+
# on : the key, one value per row: a CATime, a CATimedelta or a
|
|
46
|
+
# numeric CArray.
|
|
47
|
+
# before : how far the window reaches back from a row's key.
|
|
48
|
+
# after : how far it reaches forward.
|
|
49
|
+
# exclude : which end of the window is left out (:lower by default).
|
|
50
|
+
# by : a CArray, or an Array of them, that must match exactly:
|
|
51
|
+
# a row's window holds only the rows with the same by values.
|
|
52
|
+
# interval : the grid step of the key, for {#is_complete}.
|
|
53
|
+
# at : nil, or the points the windows sit at, a 1-D CArray of the
|
|
54
|
+
# key's kind; the answers are then one per point.
|
|
55
|
+
def initialize (value, on:, before: nil, after: nil, exclude: :lower,
|
|
56
|
+
by: nil, interval: nil, at: nil)
|
|
57
|
+
@value = value
|
|
58
|
+
unless on.is_a?(CArray) && on.ndim == 1 && on.elements == value.dim0
|
|
59
|
+
raise ArgumentError,
|
|
60
|
+
"windows: on: is a CArray of one key per row " \
|
|
61
|
+
"(#{value.dim0} rows)"
|
|
62
|
+
end
|
|
63
|
+
unless EXCLUDE.include?(exclude)
|
|
64
|
+
raise ArgumentError,
|
|
65
|
+
"windows: exclude: must be nil, :lower, :upper or :both " \
|
|
66
|
+
"(got #{exclude.inspect})"
|
|
67
|
+
end
|
|
68
|
+
if before.nil? && after.nil?
|
|
69
|
+
raise ArgumentError, "windows: give before:, after: or both"
|
|
70
|
+
end
|
|
71
|
+
unless at.nil?
|
|
72
|
+
unless by.nil?
|
|
73
|
+
raise ArgumentError, "windows: at: and by: cannot be given together"
|
|
74
|
+
end
|
|
75
|
+
unless at.is_a?(CArray) && at.ndim == 1
|
|
76
|
+
raise ArgumentError, "windows: at: is a one-dimensional CArray of points"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
@key = on
|
|
80
|
+
@n = value.dim0
|
|
81
|
+
# Taken as they are now, as the windows are: bounds reads them later.
|
|
82
|
+
@points = at&.copy
|
|
83
|
+
@nout = at.nil? ? @n : at.elements
|
|
84
|
+
@ndim = 1
|
|
85
|
+
@shape = [@nout]
|
|
86
|
+
before, after, interval = { before: before, after: after, interval: interval }.map do |kw, w|
|
|
87
|
+
CArray::DistanceLimit.read(w, on, "windows: #{kw}:")
|
|
88
|
+
end
|
|
89
|
+
@interval = interval
|
|
90
|
+
keys, bw, aw, iv, points = key_numbers(on, before, after, interval, at)
|
|
91
|
+
@before_value = before
|
|
92
|
+
@after_value = after
|
|
93
|
+
groups = group_codes(by)
|
|
94
|
+
|
|
95
|
+
# The rows that have a window, sorted by (group, key, row).
|
|
96
|
+
valid = keys.is_not_masked & groups.is_not_masked
|
|
97
|
+
valid &= keys.is_finite if keys.float?
|
|
98
|
+
valid = valid.strip_mask(false)
|
|
99
|
+
rows = CArray.int64(@n).seq[valid].copy
|
|
100
|
+
perm = CArray.sort_addr(groups.project(rows), keys.project(rows), rows)
|
|
101
|
+
@order = rows.project(perm)
|
|
102
|
+
m = @order.elements
|
|
103
|
+
sk = keys.project(@order).copy
|
|
104
|
+
sg = groups.project(@order).int64.copy
|
|
105
|
+
|
|
106
|
+
# The windows' anchors, sorted: the rows themselves, or the points that
|
|
107
|
+
# have a window (one that is masked, NaN or infinite has none).
|
|
108
|
+
if points
|
|
109
|
+
pvalid = points.is_not_masked
|
|
110
|
+
pvalid &= points.is_finite if points.float?
|
|
111
|
+
pvalid = pvalid.strip_mask(false)
|
|
112
|
+
prow = CArray.int64(@nout).seq[pvalid].copy
|
|
113
|
+
@anchor_order = prow.project(CArray.sort_addr(points.project(prow), prow))
|
|
114
|
+
sp = points.project(@anchor_order).copy
|
|
115
|
+
else
|
|
116
|
+
@anchor_order = @order
|
|
117
|
+
sp = nil
|
|
118
|
+
end
|
|
119
|
+
nwin = @anchor_order.elements
|
|
120
|
+
|
|
121
|
+
ex_lo = (exclude == :lower || exclude == :both) && !@fraction_lower
|
|
122
|
+
ex_hi = (exclude == :upper || exclude == :both) && !@fraction_upper
|
|
123
|
+
expected = iv && (bw / iv + aw / iv + 1 - (ex_lo ? 1 : 0) - (ex_hi ? 1 : 0)).round
|
|
124
|
+
if expected && expected < 1
|
|
125
|
+
raise ArgumentError,
|
|
126
|
+
"windows: before:, after: and exclude: #{exclude.inspect} leave no key " \
|
|
127
|
+
"in a window, so interval: has no grid point to check"
|
|
128
|
+
end
|
|
129
|
+
@lo, @hi, @complete =
|
|
130
|
+
CArray.__find_key_window_row_ranges__(sk, sg, bw, aw, ex_lo, ex_hi, iv, expected, sp)
|
|
131
|
+
|
|
132
|
+
# Each answer's position among the sorted windows (UNDEF for a row or a
|
|
133
|
+
# point with no window), to put the answers back in row or point order.
|
|
134
|
+
@position = CArray.int64(@nout).fill(UNDEF)
|
|
135
|
+
@position[@anchor_order] = CArray.int64(nwin).seq if nwin > 0
|
|
136
|
+
|
|
137
|
+
# The cells of every window, window after window, as rows of self.
|
|
138
|
+
counts = @hi - @lo
|
|
139
|
+
total = nwin > 0 ? counts.sum.to_i : 0
|
|
140
|
+
starts = nwin > 0 ? counts.cumsum.int64 - counts : CArray.int64(0)
|
|
141
|
+
@src_addr = window_cells(counts, starts, total)
|
|
142
|
+
@offsets = CArray.int64(nwin + 1)
|
|
143
|
+
@offsets[0] = 0
|
|
144
|
+
@offsets[1..-1] = starts + counts if nwin > 0
|
|
145
|
+
@segments = segments_of(value)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The same windows over another value with one row per key (a CAFrame
|
|
149
|
+
# column): the sorting and the window bounds are not computed again.
|
|
150
|
+
def for_value (value) # :nodoc:
|
|
151
|
+
unless value.dim0 == @n
|
|
152
|
+
raise ArgumentError,
|
|
153
|
+
"windows: the value has shape #{value.shape.inspect}; " \
|
|
154
|
+
"windows on a key take one row per key (#{@n} rows)"
|
|
155
|
+
end
|
|
156
|
+
other = dup
|
|
157
|
+
other.instance_variable_set(:@value, value)
|
|
158
|
+
other.instance_variable_set(:@segments, segments_of(value))
|
|
159
|
+
other
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @overload inspect
|
|
163
|
+
# Returns a one-line summary.
|
|
164
|
+
# @return [String]
|
|
165
|
+
def inspect
|
|
166
|
+
"#<#{self.class} rows=#{@n} before=#{@before_value.inspect} " \
|
|
167
|
+
"after=#{@after_value.inspect}>"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# @!group What each window covered
|
|
171
|
+
|
|
172
|
+
# @overload first_addr
|
|
173
|
+
# Returns the first row of each row's window (int64), UNDEF where the
|
|
174
|
+
# window is empty or the row has none.
|
|
175
|
+
# @return [CArray]
|
|
176
|
+
def first_addr
|
|
177
|
+
in_rows(edge_addr(@lo))
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @overload last_addr
|
|
181
|
+
# Returns the last row of each row's window (int64), UNDEF where the
|
|
182
|
+
# window is empty or the row has none.
|
|
183
|
+
# @return [CArray]
|
|
184
|
+
def last_addr
|
|
185
|
+
in_rows(edge_addr(@hi - 1))
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# @overload first_key
|
|
189
|
+
# Returns the key of each window's first row.
|
|
190
|
+
# @return [CArray]
|
|
191
|
+
def first_key
|
|
192
|
+
@key.project(first_addr)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# @overload last_key
|
|
196
|
+
# Returns the key of each window's last row.
|
|
197
|
+
# @return [CArray]
|
|
198
|
+
def last_key
|
|
199
|
+
@key.project(last_addr)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# @overload elements
|
|
203
|
+
# Returns the number of rows in each window (int64), masked values
|
|
204
|
+
# included; UNDEF for a row with no window.
|
|
205
|
+
# @return [CArray]
|
|
206
|
+
def elements
|
|
207
|
+
in_rows(@hi - @lo)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# @overload bounds
|
|
211
|
+
# Returns the range each window was asked to cover, as two arrays of the
|
|
212
|
+
# key's kind: the key minus `before` and the key plus `after`. Which
|
|
213
|
+
# end is left out is the `exclude:` given. A time key whose unit does
|
|
214
|
+
# not divide the width gives bounds on the finer unit the two share,
|
|
215
|
+
# so a bound is the one the windows were cut at.
|
|
216
|
+
# @return [Array(CArray, CArray)]
|
|
217
|
+
def bounds
|
|
218
|
+
[window_bound(@before_value, :-), window_bound(@after_value, :+)]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @overload is_complete
|
|
222
|
+
# Returns whether each window holds exactly the keys of the `interval`
|
|
223
|
+
# grid that starts at the row's own key: every grid point once (several
|
|
224
|
+
# rows at one point count once) and no key off the grid. UNDEF for a
|
|
225
|
+
# row with no window. Needs `interval:`. A float key is on the grid
|
|
226
|
+
# within a relative 1e-9; the window itself compares a float key with
|
|
227
|
+
# its bounds exactly, so on a decimal grid (keys i * 0.1) a key that
|
|
228
|
+
# misses a bound in the last bit can make a window hold one key more.
|
|
229
|
+
# A window that can hold no key (before: 0 and after: 0 with an end
|
|
230
|
+
# left out) has no grid point to check, so windows refuses it together
|
|
231
|
+
# with interval:.
|
|
232
|
+
# @return [CArray] boolean
|
|
233
|
+
def is_complete
|
|
234
|
+
unless @complete
|
|
235
|
+
raise ArgumentError,
|
|
236
|
+
"is_complete: give interval: to windows, the step of the key's grid"
|
|
237
|
+
end
|
|
238
|
+
in_rows(@complete)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# @!endgroup
|
|
242
|
+
|
|
243
|
+
# @!group Reductions
|
|
244
|
+
|
|
245
|
+
CArray.send(:define_named_methods, self,
|
|
246
|
+
[:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev,
|
|
247
|
+
:variancep, :stddevp, :median, :first, :last, :nunique,
|
|
248
|
+
:count, :count_not_masked, :count_masked, :all, :any,
|
|
249
|
+
:min_index, :max_index], :segments_in_rows)
|
|
250
|
+
|
|
251
|
+
private def segments_in_rows (op, *args, **kw)
|
|
252
|
+
over_columns { |seg| in_rows(seg.public_send(op, *args, **kw)) }
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# @overload percentile(p)
|
|
256
|
+
# Returns each window's p-th percentile.
|
|
257
|
+
# @return [CArray, Array<CArray>]
|
|
258
|
+
def percentile (p, **kw)
|
|
259
|
+
over_columns { |seg| in_rows(seg.percentile(p, **kw)) }
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# @overload quantile
|
|
263
|
+
# Returns each window's five-number summary `[min, Q1, median, Q3, max]`.
|
|
264
|
+
# @return [Array<CArray>]
|
|
265
|
+
def quantile (**kw)
|
|
266
|
+
over_columns { |seg| in_rows(seg.quantile(**kw)) }
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# @overload minmax
|
|
270
|
+
# Returns `[min, max]` of each window.
|
|
271
|
+
# @return [Array(CArray, CArray)]
|
|
272
|
+
def minmax (**kw)
|
|
273
|
+
over_columns { |seg| in_rows(seg.minmax(**kw)) }
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# @overload wsum(weights)
|
|
277
|
+
# Returns each window's weighted sum; `weights` is shaped like the value.
|
|
278
|
+
# @return [CArray]
|
|
279
|
+
def wsum (weights, **kw)
|
|
280
|
+
over_columns { |seg, j| in_rows(seg.wsum(cells_of(weights, j), **kw)) }
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# @overload wmean(weights)
|
|
284
|
+
# Returns each window's weighted mean.
|
|
285
|
+
# @return [CArray]
|
|
286
|
+
def wmean (weights, **kw)
|
|
287
|
+
over_columns { |seg, j| in_rows(seg.wmean(cells_of(weights, j), **kw)) }
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# @overload min_addr(min_count: nil, fill_value: nil)
|
|
291
|
+
# Returns the row of each window's minimum (int64).
|
|
292
|
+
# @return [CArray]
|
|
293
|
+
def min_addr (fill_value: nil, **kw)
|
|
294
|
+
over_columns { |seg| winner_row(seg.min_addr(**kw), fill_value) }
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# @overload max_addr(min_count: nil, fill_value: nil)
|
|
298
|
+
# Returns the row of each window's maximum (int64).
|
|
299
|
+
# @return [CArray]
|
|
300
|
+
def max_addr (fill_value: nil, **kw)
|
|
301
|
+
over_columns { |seg| winner_row(seg.max_addr(**kw), fill_value) }
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# @overload reduce(*args, data_type: nil) { |piece| ... }
|
|
305
|
+
# Folds each window with the block, as {CASegmentIterator#reduce}.
|
|
306
|
+
# @return [CArray]
|
|
307
|
+
def reduce (*args, **kw, &block)
|
|
308
|
+
if @segments.is_a?(Array)
|
|
309
|
+
raise NotImplementedError,
|
|
310
|
+
"reduce: windows over a value of several columns (shape " \
|
|
311
|
+
"#{@value.shape.inspect}) have no block fold; reduce a column " \
|
|
312
|
+
"at a time (value[nil, j])"
|
|
313
|
+
end
|
|
314
|
+
in_rows(@segments.reduce(*args, **kw, &block))
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# @overload each { |piece| ... }
|
|
318
|
+
# Yields each row's window, in row order; a row with no window yields an
|
|
319
|
+
# empty array.
|
|
320
|
+
# @return [self, Enumerator]
|
|
321
|
+
def each
|
|
322
|
+
return to_enum(:each) unless block_given?
|
|
323
|
+
pieces = []
|
|
324
|
+
if @segments.is_a?(Array)
|
|
325
|
+
rest = [nil] * (@value.ndim - 1)
|
|
326
|
+
(@offsets.elements - 1).times do |p|
|
|
327
|
+
rows = @src_addr[@offsets[p]...@offsets[p + 1]]
|
|
328
|
+
pieces << @value[rows, *rest]
|
|
329
|
+
end
|
|
330
|
+
else
|
|
331
|
+
@segments.each { |piece| pieces << piece }
|
|
332
|
+
end
|
|
333
|
+
empty = @value[0...0, *[nil] * (@value.ndim - 1)]
|
|
334
|
+
@nout.times do |r|
|
|
335
|
+
p = @position[r]
|
|
336
|
+
yield(UNDEF.equal?(p) ? empty : pieces[p])
|
|
337
|
+
end
|
|
338
|
+
self
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# @!endgroup
|
|
342
|
+
|
|
343
|
+
private
|
|
344
|
+
|
|
345
|
+
# Answers per sorted row, put back in row order.
|
|
346
|
+
def in_rows (result)
|
|
347
|
+
return result.map { |r| in_rows(r) } if result.is_a?(Array)
|
|
348
|
+
result.project(@position)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# A winner's address among the gathered cells, as a row of self, in row
|
|
352
|
+
# order; fill_value fills the windows with none, as the core fills it.
|
|
353
|
+
def winner_row (addr, fill_value)
|
|
354
|
+
out = in_rows(@src_addr.project(addr))
|
|
355
|
+
out[out.is_masked] = fill_value unless fill_value.nil?
|
|
356
|
+
out
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# The row at sorted position pos[i] of the window of sorted row i, UNDEF
|
|
360
|
+
# where the window is empty.
|
|
361
|
+
def edge_addr (pos)
|
|
362
|
+
return CArray.int64(0) if pos.elements == 0
|
|
363
|
+
out = @order.project(pos)
|
|
364
|
+
out[(@hi - @lo).le(0)] = UNDEF
|
|
365
|
+
out
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# A weights array taken through the same cells as the value: one weight
|
|
369
|
+
# per row, or for a value of several columns, weights shaped like it
|
|
370
|
+
# (column +j+ of them).
|
|
371
|
+
def cells_of (weights, j = nil)
|
|
372
|
+
return weights unless weights.is_a?(CArray)
|
|
373
|
+
if j && weights.ndim > 1 && weights.dim0 == @n
|
|
374
|
+
weights = weights.reshape(@n, weights.elements / @n)[nil, j]
|
|
375
|
+
end
|
|
376
|
+
return weights unless weights.elements == @n
|
|
377
|
+
weights.project(@src_addr)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# The segment iterators the reductions run on: one for a value with one
|
|
381
|
+
# cell per row, and for a value of several columns (each row a profile)
|
|
382
|
+
# one per trailing column, every one over the same windows.
|
|
383
|
+
def segments_of (value)
|
|
384
|
+
return CASegmentIterator.new(value.project(@src_addr), offsets: @offsets) if value.ndim == 1
|
|
385
|
+
width = value.shape[1..].inject(1, :*)
|
|
386
|
+
flat = value.reshape(@n, width)
|
|
387
|
+
Array.new(width) do |j|
|
|
388
|
+
CASegmentIterator.new(flat[nil, j].project(@src_addr), offsets: @offsets)
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# A reduction of the windows: as it is for a value with one cell per row;
|
|
393
|
+
# for a value of several columns, of each column, the answers laid side
|
|
394
|
+
# by side in the value's trailing shape, as a group reduces a profile
|
|
395
|
+
# column along the rows.
|
|
396
|
+
def over_columns (&block)
|
|
397
|
+
return block.call(@segments, nil) unless @segments.is_a?(Array)
|
|
398
|
+
trail = @value.shape[1..]
|
|
399
|
+
if @segments.empty?
|
|
400
|
+
one = block.call(CASegmentIterator.new(CArray.new(@value.data_type, [0], bytes: @value.bytes),
|
|
401
|
+
offsets: CArray.int64(1)), nil)
|
|
402
|
+
return lay_out(Array.new(0), one, trail)
|
|
403
|
+
end
|
|
404
|
+
results = @segments.each_with_index.map { |seg, j| block.call(seg, j) }
|
|
405
|
+
lay_out(results, results.first, trail)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Answers of the columns (each one per row) as one array of the rows and
|
|
409
|
+
# the trailing shape; an Array of answers (minmax, quantile) answer by
|
|
410
|
+
# answer.
|
|
411
|
+
def lay_out (results, sample, trail)
|
|
412
|
+
if sample.is_a?(Array)
|
|
413
|
+
return sample.each_index.map { |k| lay_out(results.map { |r| r[k] }, sample[k], trail) }
|
|
414
|
+
end
|
|
415
|
+
out = CArray.new(sample.data_type, [@nout, *trail], bytes: sample.bytes)
|
|
416
|
+
flat = out.reshape(@nout, results.size)
|
|
417
|
+
results.each_with_index { |r, j| flat[nil, j] = r }
|
|
418
|
+
out
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# The rows of every window, window after window.
|
|
422
|
+
def window_cells (counts, starts, total)
|
|
423
|
+
return CArray.int64(0) if total == 0
|
|
424
|
+
nz = counts.gt(0)
|
|
425
|
+
owner = CArray.int64(counts.elements).seq[nz].copy
|
|
426
|
+
first = starts[nz].copy
|
|
427
|
+
mark = CArray.int64(total).fill(0)
|
|
428
|
+
mark[first] = 1
|
|
429
|
+
run = mark.cumsum.int64 - 1
|
|
430
|
+
anchor = owner.project(run)
|
|
431
|
+
pos = @lo.project(anchor) + (CArray.int64(total).seq - first.project(run))
|
|
432
|
+
@order.project(pos)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# The keys and widths as numbers the walk compares: ticks for a time or a
|
|
436
|
+
# duration key, the values for a numeric one. Widths that are not whole
|
|
437
|
+
# in the key's ticks scale both up to a finer grid. A window is decided
|
|
438
|
+
# by differences of keys alone, so integer keys are measured from their
|
|
439
|
+
# smallest value: that is what lets a uint64 key, or ticks on a finer
|
|
440
|
+
# grid, be compared as int64.
|
|
441
|
+
#
|
|
442
|
+
# Points (at:) are converted together with the keys -- joined, converted
|
|
443
|
+
# once, and split -- so that both are measured from the same origin on
|
|
444
|
+
# the same grid.
|
|
445
|
+
def key_numbers (key, before, after, interval, points = nil)
|
|
446
|
+
if key.is_a?(CATime) || key.is_a?(CATimedelta)
|
|
447
|
+
if points
|
|
448
|
+
unless points.instance_of?(key.class)
|
|
449
|
+
raise ArgumentError,
|
|
450
|
+
"windows: at: holds points of the key's kind, a #{key.class} " \
|
|
451
|
+
"(got #{CArray::Inspector.type_label(points)})"
|
|
452
|
+
end
|
|
453
|
+
unless CATimeUnitAlgebra.same_group?(key.unit.base, points.unit.base)
|
|
454
|
+
raise ArgumentError,
|
|
455
|
+
"windows: at: holds points in #{points.unit.count} #{points.unit.base}, " \
|
|
456
|
+
"which cannot be measured on a key in #{key.unit.count} #{key.unit.base} " \
|
|
457
|
+
"(months and years have no fixed length); convert one with to_unit"
|
|
458
|
+
end
|
|
459
|
+
unit = CATimeUnitAlgebra.finer(key.unit, points.unit)
|
|
460
|
+
key, points = key.to_unit(unit), points.to_unit(unit)
|
|
461
|
+
end
|
|
462
|
+
widths = [before, after, interval].map { |w| w.nil? ? nil : duration_in_ticks(w, key.unit) }
|
|
463
|
+
scale = widths.compact.map(&:denominator).inject(1, :lcm)
|
|
464
|
+
ticks = from_smallest(joined(key.ticks, points&.ticks), scale)
|
|
465
|
+
bw, aw, iv = widths.map { |w| w && (w * scale).to_i }
|
|
466
|
+
bw ||= 0
|
|
467
|
+
aw ||= 0
|
|
468
|
+
check_widths(bw, aw, iv)
|
|
469
|
+
ticks, points = split(ticks, points)
|
|
470
|
+
return [ticks, bw, aw, iv, points]
|
|
471
|
+
end
|
|
472
|
+
unless key.numeric? && !key.complex?
|
|
473
|
+
raise ArgumentError,
|
|
474
|
+
"windows: on: is a time, a duration or a real number key " \
|
|
475
|
+
"(got #{CArray::Inspector.type_label(key)})"
|
|
476
|
+
end
|
|
477
|
+
if points && (points.is_a?(CATime) || points.is_a?(CATimedelta) ||
|
|
478
|
+
!points.numeric? || points.complex?)
|
|
479
|
+
raise ArgumentError,
|
|
480
|
+
"windows: at: holds points of the key's kind, real numbers " \
|
|
481
|
+
"(got #{CArray::Inspector.type_label(points)})"
|
|
482
|
+
end
|
|
483
|
+
both = joined(key, points)
|
|
484
|
+
widths = [before, after, interval]
|
|
485
|
+
if both.integer? && (interval.nil? || whole?(interval))
|
|
486
|
+
# Integer keys are compared as integers whatever the widths are
|
|
487
|
+
# given as: k >= t - w holds for an integer k exactly when
|
|
488
|
+
# k >= t - floor(w), and when w is not whole, k > t - w does too, so
|
|
489
|
+
# a bound at a fraction is the bound at its floor, included. An
|
|
490
|
+
# infinite width leaves its side open; the keys are then measured
|
|
491
|
+
# from the smallest, so that a bound at the int64 limit is beyond
|
|
492
|
+
# every key.
|
|
493
|
+
open = [before, after].any? { |w| w.is_a?(Float) && w.infinite? }
|
|
494
|
+
bw, aw = [before, after].map { |w| w.nil? ? 0 : integer_width(w) }
|
|
495
|
+
iv = interval && interval.to_r.to_i
|
|
496
|
+
check_widths(bw, aw, iv)
|
|
497
|
+
@fraction_lower = !before.nil? && !whole?(before)
|
|
498
|
+
@fraction_upper = !after.nil? && !whole?(after)
|
|
499
|
+
numbers, points = split(from_smallest(both, 1, shift: open), points)
|
|
500
|
+
[numbers, bw, aw, iv, points]
|
|
501
|
+
elsif both.integer?
|
|
502
|
+
# A fractional interval on integer keys: compared as float64, which
|
|
503
|
+
# is exact while the keys span less than 2**53.
|
|
504
|
+
bw, aw, iv = widths.map { |w| w && w.to_f }
|
|
505
|
+
bw ||= 0.0
|
|
506
|
+
aw ||= 0.0
|
|
507
|
+
check_widths(bw, aw, iv)
|
|
508
|
+
numbers = from_smallest(both, 1, shift: true)
|
|
509
|
+
if numbers.elements > 0 && (span = numbers.max) != UNDEF && span > 2**53
|
|
510
|
+
raise ArgumentError,
|
|
511
|
+
"windows: integer keys spanning #{span} cannot be compared with " \
|
|
512
|
+
"a fractional interval: exactly; give a whole interval:"
|
|
513
|
+
end
|
|
514
|
+
numbers, points = split(numbers.float64, points)
|
|
515
|
+
[numbers, bw, aw, iv, points]
|
|
516
|
+
else
|
|
517
|
+
bw, aw, iv = widths.map { |w| w && w.to_f }
|
|
518
|
+
bw ||= 0.0
|
|
519
|
+
aw ||= 0.0
|
|
520
|
+
check_widths(bw, aw, iv)
|
|
521
|
+
numbers, points = split(both.float64, points)
|
|
522
|
+
[numbers, bw, aw, iv, points]
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# Whether a width is a whole number (an infinite one is not).
|
|
527
|
+
def whole? (w)
|
|
528
|
+
!(w.is_a?(Float) && w.infinite?) && w.to_r.denominator == 1
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
# A width on integer keys as an integer: its floor, or the int64 limit
|
|
532
|
+
# for an infinite one.
|
|
533
|
+
def integer_width (w)
|
|
534
|
+
return 2**63 - 1 if w.is_a?(Float) && w.infinite?
|
|
535
|
+
[w.to_r.floor, 2**63 - 1].min
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# The keys followed by the points, one array, when there are points.
|
|
539
|
+
def joined (keys, points)
|
|
540
|
+
points ? CArray.concatenate([keys, points]) : keys
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# The keys and the points (nil without them) back out of a joined array.
|
|
544
|
+
def split (numbers, points)
|
|
545
|
+
return [numbers, nil] unless points
|
|
546
|
+
[numbers[0...@n].copy, numbers[@n...numbers.elements].copy]
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def window_bound (width, op)
|
|
550
|
+
key = @points || @key
|
|
551
|
+
return key if width.nil?
|
|
552
|
+
if key.is_a?(CATime) || key.is_a?(CATimedelta)
|
|
553
|
+
key = key.to_unit(CATimeUnitAlgebra.finer(key.unit, width.unit))
|
|
554
|
+
end
|
|
555
|
+
key.public_send(op, width)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
# A duration in ticks of the key's unit, as a Rational.
|
|
559
|
+
def duration_in_ticks (w, unit)
|
|
560
|
+
count, base = w.value * w.unit.count, w.unit.base
|
|
561
|
+
unless CATimeUnitAlgebra.same_group?(base, unit.base)
|
|
562
|
+
raise ArgumentError,
|
|
563
|
+
"windows: a width of #{count} #{base} cannot be measured on a key in " \
|
|
564
|
+
"#{unit.count} #{unit.base} (months and years have no fixed length)"
|
|
565
|
+
end
|
|
566
|
+
Rational(count) * CATimeUnitAlgebra.base_ratio(base) /
|
|
567
|
+
(unit.count * CATimeUnitAlgebra.base_ratio(unit.base))
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
# An integer key as int64 numbers scaled by `scale`: the values
|
|
571
|
+
# themselves when they fit, else measured from the smallest value,
|
|
572
|
+
# refused when even the span does not fit.
|
|
573
|
+
def from_smallest (key, scale, shift: false)
|
|
574
|
+
lo = key.min
|
|
575
|
+
return key.int64 if lo == UNDEF
|
|
576
|
+
hi = key.max
|
|
577
|
+
if !shift && lo * scale >= -2**63 && hi * scale <= 2**63 - 1
|
|
578
|
+
return scale == 1 ? key.int64 : key.int64 * scale
|
|
579
|
+
end
|
|
580
|
+
span = hi - lo
|
|
581
|
+
if span * scale > 2**63 - 1
|
|
582
|
+
raise ArgumentError,
|
|
583
|
+
"windows: the keys span #{span}#{scale == 1 ? "" : " ticks, #{span * scale} " "on the grid the widths need,"} more than an int64 holds"
|
|
584
|
+
end
|
|
585
|
+
# uint64 subtracts in its own type, which holds every difference; any
|
|
586
|
+
# other integer type is widened first, so the subtraction cannot wrap.
|
|
587
|
+
numbers = key.data_type == CA_UINT64 ? (key - lo).int64 : key.int64 - lo
|
|
588
|
+
scale == 1 ? numbers : numbers * scale
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def check_widths (bw, aw, iv)
|
|
592
|
+
return unless iv
|
|
593
|
+
unless iv > 0
|
|
594
|
+
raise ArgumentError, "windows: interval: is positive"
|
|
595
|
+
end
|
|
596
|
+
[bw, aw].each do |w|
|
|
597
|
+
q = w.to_f / iv
|
|
598
|
+
next if (q - q.round).abs <= 1e-9 * [1.0, q.abs].max
|
|
599
|
+
raise ArgumentError,
|
|
600
|
+
"windows: before: and after: are whole numbers of interval:, " \
|
|
601
|
+
"so a complete window has a definite set of keys"
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
# A code per row for the by: keys, equal codes for equal keys, none for a
|
|
606
|
+
# row with a masked by: cell; zeros when there is no by:.
|
|
607
|
+
def group_codes (by)
|
|
608
|
+
return CArray.int64(@n).fill(0) if by.nil?
|
|
609
|
+
keys = by.is_a?(Array) ? by : [by]
|
|
610
|
+
if keys.empty?
|
|
611
|
+
raise ArgumentError, "windows: by: names no key"
|
|
612
|
+
end
|
|
613
|
+
keys.each do |k|
|
|
614
|
+
unless k.is_a?(CArray) && k.ndim == 1 && k.elements == @n
|
|
615
|
+
raise ArgumentError, "windows: by: is a CArray of one value per row (#{@n} rows)"
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
CACategorical.send(:from_keys, keys).codes
|
|
619
|
+
end
|
|
620
|
+
end
|