carray 3.0.1 → 3.0.2
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 +520 -0
- data/README.md +2 -2
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +33 -4
- data/ext/ca_axis_group.c +202 -96
- data/ext/ca_categorical_iterator.c +108 -54
- data/ext/ca_kernel_iterator.c +317 -51
- data/ext/ca_kernel_iterator.h +142 -35
- data/ext/ca_obj_array.c +62 -20
- data/ext/ca_obj_block.c +4 -4
- data/ext/ca_obj_const_string.c +85 -26
- data/ext/ca_obj_face.c +24 -0
- data/ext/ca_obj_face.h +15 -0
- data/ext/ca_obj_fixlen_string.c +18 -5
- data/ext/ca_obj_meld.c +123 -25
- data/ext/ca_obj_object.c +8 -0
- data/ext/ca_obj_select.c +49 -34
- data/ext/ca_obj_stack.c +3 -8
- data/ext/ca_obj_stride.c +72 -1
- data/ext/ca_obj_string.c +8 -4
- data/ext/ca_obj_window.c +8 -2
- data/ext/ca_op_ipower.c +1 -2
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sweep_engine.c +307 -143
- data/ext/ca_sweep_engine.h +26 -5
- data/ext/carray.h +21 -2
- data/ext/carray_access.c +32 -20
- data/ext/carray_address_basis.c +590 -0
- data/ext/carray_broadcast.c +3 -3
- data/ext/carray_call_cfunc.c +667 -483
- data/ext/carray_cast.c +115 -41
- data/ext/carray_copy.c +55 -30
- data/ext/carray_core.c +83 -3
- data/ext/carray_count.c +9 -10
- data/ext/carray_factorize.c +46 -25
- data/ext/carray_internal.h +17 -0
- data/ext/carray_kernels_reduce_aggregate.c +168 -0
- data/ext/carray_kernels_reduce_cumulative.c +270 -1
- data/ext/carray_kernels_reduce_extreme.c +554 -8
- data/ext/carray_kernels_scan.c +4 -4
- data/ext/carray_kernels_search.c +94 -14
- data/ext/carray_loop.c +7 -1
- data/ext/carray_mask.c +23 -8
- data/ext/carray_median_percentile.c +55 -0
- data/ext/carray_operator.c +4 -4
- data/ext/carray_order.c +1 -1
- data/ext/carray_random.c +384 -40
- data/ext/carray_slab.c +13 -0
- data/ext/carray_sort.c +20 -22
- data/ext/mk_call_cfunc.rb +103 -116
- data/ext/mkkernel.rb +297 -29
- data/ext/ruby_carray.c +10 -1
- data/ext/version.h +4 -4
- data/lib/carray/autoload_carray.rb +5 -3
- data/lib/carray/autoload_method_extension.rb +12 -0
- data/lib/carray/axis_group.rb +77 -0
- data/lib/carray/basics.rb +4 -0
- data/lib/carray/block_iterator.rb +92 -16
- data/lib/carray/categorical.rb +150 -33
- data/lib/carray/categorical_iterator.rb +207 -80
- data/lib/carray/const_string.rb +131 -27
- data/lib/carray/construct.rb +40 -0
- data/lib/carray/data_type_extension.rb +3 -0
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/csv_parser.rb +11 -4
- data/lib/carray/frame/frame.rb +81 -10
- data/lib/carray/frame/group.rb +36 -3
- data/lib/carray/frame/io.rb +67 -15
- data/lib/carray/frame/records.rb +18 -4
- data/lib/carray/frame/verbs.rb +14 -11
- data/lib/carray/inspect.rb +42 -9
- data/lib/carray/iterator.rb +143 -0
- data/lib/carray/lazy.rb +0 -37
- data/lib/carray/mask_gap_fill.rb +3 -1
- data/lib/carray/methods/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- data/lib/carray/methods/is_in.rb +13 -2
- data/lib/carray/methods/locate_addr.rb +75 -2
- data/lib/carray/methods/mask_duplicates.rb +35 -1
- data/lib/carray/methods/nunique.rb +22 -1
- data/lib/carray/methods/repeat.rb +110 -0
- data/lib/carray/methods/unique.rb +41 -1
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/slab_iterator.rb +58 -13
- data/lib/carray/string_operation_extension.rb +5 -1
- data/lib/carray/time.rb +18 -2
- data/lib/carray/window_iterator.rb +142 -20
- data/lib/carray.rb +2 -0
- data/yard-stubs/ca_obj_block.rb +2 -7
- data/yard-stubs/ca_obj_window.rb +10 -2
- data/yard-stubs/carray_access.rb +1 -1
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_core.rb +0 -80
- data/yard-stubs/carray_count.rb +7 -2
- data/yard-stubs/carray_lazy.rb +205 -0
- data/yard-stubs/carray_math.rb +1486 -3
- data/yard-stubs/carray_median_percentile.rb +16 -2
- data/yard-stubs/carray_order.rb +9 -69
- data/yard-stubs/carray_slab.rb +9 -7
- data/yard-stubs/carray_sort.rb +7 -5
- metadata +9 -1
data/lib/carray/frame/io.rb
CHANGED
|
@@ -11,12 +11,27 @@ class CAFrame
|
|
|
11
11
|
# later. Broken cells fail to_type and become UNDEF automatically
|
|
12
12
|
# (parse-mask, §6-2).
|
|
13
13
|
#
|
|
14
|
+
# +source+ is a path, or an open IO -- anything answering +gets+, which a
|
|
15
|
+
# StringIO is. So CSV that is already in memory does not have to go to a
|
|
16
|
+
# temporary file first:
|
|
17
|
+
#
|
|
18
|
+
# CAFrame.from_csv("obs.csv") # a path
|
|
19
|
+
# CAFrame.from_csv(StringIO.new(body)) # text already in hand
|
|
20
|
+
# File.open("obs.csv") { |io| CAFrame.from_csv(io) }
|
|
21
|
+
#
|
|
22
|
+
# A String is always read as a path, never as CSV text: guessing between the
|
|
23
|
+
# two by looking for a newline is the kind of guess that is right until it is
|
|
24
|
+
# not, and StringIO says which one you meant in six characters. An IO is read
|
|
25
|
+
# from where it is and left open -- the caller opened it and closes it.
|
|
26
|
+
#
|
|
14
27
|
# Parsing uses the built-in fast tokenizer (CSVParser). Options:
|
|
15
28
|
# sep: field separator (default ",")
|
|
16
29
|
# quote: quote character (default '"')
|
|
17
30
|
# strip: trim spaces from unquoted fields (default false, RFC spacing)
|
|
18
|
-
# +encoding+:
|
|
19
|
-
#
|
|
31
|
+
# +encoding+: open-mode encoding for a path (default "bom|utf-8", strips a
|
|
32
|
+
# BOM). It has nothing to open when +source+ is an IO, so there
|
|
33
|
+
# the IO's own encoding governs and a BOM is the caller's.
|
|
34
|
+
# parser: a callable source -> [headers, rows] to inject another parser
|
|
20
35
|
# (e.g. the stdlib +csv+, or a typed-table source); when given,
|
|
21
36
|
# sep/quote/strip/encoding and any block are that parser's concern.
|
|
22
37
|
#
|
|
@@ -28,25 +43,25 @@ class CAFrame
|
|
|
28
43
|
# skip 2; header; skip 1; body
|
|
29
44
|
# end
|
|
30
45
|
#
|
|
46
|
+
# A missing field -- an unquoted empty one, or a cell a short row never
|
|
47
|
+
# reached -- is UNDEF in the frame, whether or not the column is cast by
|
|
48
|
+
# +types:+. A quoted empty field ("") is the empty string, which is a value.
|
|
49
|
+
# So the mask +to_csv+ writes comes back as a mask.
|
|
50
|
+
#
|
|
31
51
|
# Columns are handed to the frame as CABlock views over one backing object
|
|
32
52
|
# array (§3.6 view-by-default); casting a column materializes it, and +copy+
|
|
33
53
|
# gives an independent frame.
|
|
34
|
-
def self.from_csv(
|
|
54
|
+
def self.from_csv(source, types: nil,
|
|
35
55
|
sep: ",", quote: '"', strip: false,
|
|
36
56
|
encoding: "bom|utf-8", parser: nil, &block)
|
|
37
57
|
names, rows =
|
|
38
58
|
if parser
|
|
39
|
-
parser.call(
|
|
59
|
+
parser.call(source)
|
|
60
|
+
elsif source.respond_to?(:gets)
|
|
61
|
+
read_csv(source, sep: sep, quote: quote, strip: strip, &block)
|
|
40
62
|
else
|
|
41
|
-
File.open(
|
|
42
|
-
|
|
43
|
-
if block
|
|
44
|
-
block.arity == 1 ? block.call(reader) : reader.instance_exec(&block)
|
|
45
|
-
else
|
|
46
|
-
reader.header
|
|
47
|
-
reader.body
|
|
48
|
-
end
|
|
49
|
-
reader.result
|
|
63
|
+
File.open(source, "r:#{encoding}") do |io|
|
|
64
|
+
read_csv(io, sep: sep, quote: quote, strip: strip, &block)
|
|
50
65
|
end
|
|
51
66
|
end
|
|
52
67
|
|
|
@@ -55,6 +70,22 @@ class CAFrame
|
|
|
55
70
|
frame
|
|
56
71
|
end
|
|
57
72
|
|
|
73
|
+
# Drive the reading-control DSL over one open IO and hand back
|
|
74
|
+
# [names, rows]. Shared by the path and the IO source, so the two cannot
|
|
75
|
+
# come to read a file differently.
|
|
76
|
+
def self.read_csv (io, sep:, quote:, strip:, &block)
|
|
77
|
+
reader = CSVReader.new(io, sep: sep, quote: quote, strip: strip)
|
|
78
|
+
if block
|
|
79
|
+
block.arity == 1 ? block.call(reader) : reader.instance_exec(&block)
|
|
80
|
+
else
|
|
81
|
+
reader.header
|
|
82
|
+
reader.body
|
|
83
|
+
end
|
|
84
|
+
reader.result
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private_class_method :read_csv
|
|
88
|
+
|
|
58
89
|
# Build a frame from parsed [names, rows]. When names is nil (headerless and
|
|
59
90
|
# no column_names) positional names "c0".."cN" are generated from the widest
|
|
60
91
|
# row. Rows are squared off to the column count (short rows padded with nil,
|
|
@@ -159,7 +190,9 @@ class CAFrame
|
|
|
159
190
|
parts = @columns.map { |k, v| "#{k}:#{v.data_type}#{v.ndim > 1 ? v.shape[1..].inspect : ''}" }
|
|
160
191
|
idx = @index ? " index=#{@axis_name.inspect}" : ""
|
|
161
192
|
head = "#<CAFrame nrow=#{@nrow} vars=[#{parts.join(', ')}]#{idx}>"
|
|
162
|
-
|
|
193
|
+
# The table counts the index as a column, so a frame whose only data is its
|
|
194
|
+
# index has one to show. Gate on the same thing render_table does.
|
|
195
|
+
return head if @columns.empty? && @index.nil?
|
|
163
196
|
head + "\n" + render_table(head: 8, tail: 2, index: true, precision: 6,
|
|
164
197
|
footer: false)
|
|
165
198
|
end
|
|
@@ -252,6 +285,15 @@ class CAFrame
|
|
|
252
285
|
"#{n} #{noun}#{n == 1 ? '' : 's'}"
|
|
253
286
|
end
|
|
254
287
|
|
|
288
|
+
# Render one N-D cell the way Array#inspect would, except that a masked
|
|
289
|
+
# element prints as the table's missing marker rather than as UNDEF.
|
|
290
|
+
private def format_nested_cell(v)
|
|
291
|
+
case v
|
|
292
|
+
when Array then "[" + v.map { |x| format_nested_cell(x) }.join(", ") + "]"
|
|
293
|
+
else UNDEF.equal?(v) || v.nil? ? "_" : v.inspect
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
255
297
|
private def format_table_cell(col, i, precision)
|
|
256
298
|
e = elem_at(col, i)
|
|
257
299
|
if UNDEF.equal?(e) || e.nil?
|
|
@@ -262,7 +304,10 @@ class CAFrame
|
|
|
262
304
|
# to read. precision: nil prints the value as Ruby renders it.
|
|
263
305
|
e.round(precision).to_s
|
|
264
306
|
elsif e.is_a?(CArray)
|
|
265
|
-
|
|
307
|
+
# An N-D cell renders its elements, and a masked element among them
|
|
308
|
+
# takes the same marker a masked scalar does -- UNDEF's own inspect
|
|
309
|
+
# would put a second spelling of "missing" in the same table.
|
|
310
|
+
format_nested_cell(e.to_a)
|
|
266
311
|
elsif e.is_a?(String)
|
|
267
312
|
e
|
|
268
313
|
elsif e.respond_to?(:iso8601)
|
|
@@ -314,6 +359,13 @@ class CAFrame
|
|
|
314
359
|
end
|
|
315
360
|
end
|
|
316
361
|
table = CArray.object(rows.size, ncol) { rows }
|
|
362
|
+
# A missing field is UNDEF, not a Ruby nil sitting in a cell. The
|
|
363
|
+
# tokenizer says missing with nil (an unquoted empty field; a quoted
|
|
364
|
+
# one is the empty string and stays a value), and an object array will
|
|
365
|
+
# hold that nil quite happily -- so a column read without `types:` used
|
|
366
|
+
# to carry nil where the same column read with one carried UNDEF, and
|
|
367
|
+
# the mask a to_csv had written did not survive the trip back.
|
|
368
|
+
table[:eq, nil] = UNDEF
|
|
317
369
|
names.each_with_index { |name, j| cols[name] = table[nil, j] }
|
|
318
370
|
new(cols)
|
|
319
371
|
end
|
data/lib/carray/frame/records.rb
CHANGED
|
@@ -59,30 +59,44 @@ class CAFrame
|
|
|
59
59
|
# native scalar column (nil -> UNDEF); anything else -> object column.
|
|
60
60
|
def self.build_record_column(values, n)
|
|
61
61
|
present = values.reject(&:nil?)
|
|
62
|
-
return CArray.object(n) { values } if present.empty?
|
|
62
|
+
return mask_missing(CArray.object(n) { values }) if present.empty?
|
|
63
63
|
|
|
64
64
|
if present.all? { |v| v.is_a?(Array) || v.is_a?(CArray) }
|
|
65
65
|
build_nd_column(values, present, n)
|
|
66
66
|
else
|
|
67
67
|
type = numeric_leaf_type(present)
|
|
68
68
|
col = CArray.object(n) { values }
|
|
69
|
-
type ? col.to_type(type) : col
|
|
69
|
+
type ? col.to_type(type) : mask_missing(col)
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
private_class_method :build_record_column
|
|
73
73
|
|
|
74
|
+
# A missing cell is UNDEF, not a Ruby nil sitting in a cell. to_records
|
|
75
|
+
# writes a masked cell as nil, so nil on the way back in is the only
|
|
76
|
+
# spelling a missing cell has; to_type does this conversion for a numeric
|
|
77
|
+
# column, and an object column would otherwise keep the nil as a value --
|
|
78
|
+
# a row with no label coming back as a row labelled nil. The CSV reader
|
|
79
|
+
# takes the same position (see build_frame in io.rb).
|
|
80
|
+
def self.mask_missing(col)
|
|
81
|
+
col[:eq, nil] = UNDEF
|
|
82
|
+
col
|
|
83
|
+
end
|
|
84
|
+
private_class_method :mask_missing
|
|
85
|
+
|
|
74
86
|
# Stack equal-length array cells into an (N, L) column via an object 2-D fill
|
|
75
87
|
# + to_type (nil rows -> UNDEF, int/float by leaf). Ragged lengths or
|
|
76
88
|
# non-numeric leaves fall back to a 1-D object column of the raw cells.
|
|
77
89
|
def self.build_nd_column(values, present, n)
|
|
78
90
|
lengths = present.map { |v| v.is_a?(CArray) ? v.shape[0] : v.size }
|
|
79
91
|
len = lengths.first
|
|
80
|
-
|
|
92
|
+
unless lengths.all? { |x| x == len }
|
|
93
|
+
return mask_missing(CArray.object(n) { values })
|
|
94
|
+
end
|
|
81
95
|
|
|
82
96
|
nested = values.map { |v| v.nil? ? Array.new(len) : (v.is_a?(CArray) ? v.to_a : v) }
|
|
83
97
|
type = numeric_leaf_type(nested.flatten.compact)
|
|
84
98
|
table = CArray.object(n, len) { nested }
|
|
85
|
-
type ? table.to_type(type) : table
|
|
99
|
+
type ? table.to_type(type) : mask_missing(table)
|
|
86
100
|
end
|
|
87
101
|
private_class_method :build_nd_column
|
|
88
102
|
|
data/lib/carray/frame/verbs.rb
CHANGED
|
@@ -230,7 +230,9 @@ class CAFrame
|
|
|
230
230
|
return
|
|
231
231
|
end
|
|
232
232
|
yvalid = col.value.float64[present]
|
|
233
|
-
|
|
233
|
+
# mask_invalid before the cast: linear_fetch marks out of range with NaN,
|
|
234
|
+
# and casting back to an integer column would turn it into a plausible 0.
|
|
235
|
+
col[] = yvalid.linear_fetch(addr).mask_invalid.to_type(col.data_type) # write-through
|
|
234
236
|
end
|
|
235
237
|
|
|
236
238
|
private def cast_one(name, type)
|
|
@@ -267,16 +269,17 @@ class CAFrame
|
|
|
267
269
|
"promote takes a data type Symbol (got #{type.class}); " \
|
|
268
270
|
"class-shaped targets are not promotion destinations"
|
|
269
271
|
end
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
272
|
+
# Check every column before rebinding any. A column that would narrow
|
|
273
|
+
# rejects the whole promote, and rejecting part way through would leave the
|
|
274
|
+
# frame promoted in whichever columns happened to come first.
|
|
275
|
+
#
|
|
276
|
+
# A Face column answers for itself: :object is its surface values, a
|
|
277
|
+
# numeric target is whatever it declares in #to_numeric (and a TypeError
|
|
278
|
+
# naming that method when it declares nothing). result_type has nothing
|
|
279
|
+
# to say about a surface it cannot read, so the widening check -- which
|
|
280
|
+
# is about primitive promotion -- applies to plain columns only.
|
|
281
|
+
@columns.each { |key, col| refuse_narrowing(key, col, type) unless col.face? }
|
|
282
|
+
@columns.each_key { |key| @columns[key] = @columns[key].to_type(type) }
|
|
280
283
|
end
|
|
281
284
|
|
|
282
285
|
private def refuse_narrowing(key, col, type)
|
data/lib/carray/inspect.rb
CHANGED
|
@@ -7,7 +7,12 @@ class CArray::Inspector # :nodoc:
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
# @!visibility private
|
|
10
|
-
|
|
10
|
+
#
|
|
11
|
+
# `abbrev` false renders every element instead of eliding with `...`.
|
|
12
|
+
# It is the one difference between #inspect and #inspect_full: the
|
|
13
|
+
# header and the layout are the same, so there is one renderer rather
|
|
14
|
+
# than two that could drift apart.
|
|
15
|
+
def inspect_string (abbrev: true)
|
|
11
16
|
if @carray.ndim == 0
|
|
12
17
|
raise "can't inspect CArray of ndim == 0"
|
|
13
18
|
end
|
|
@@ -15,7 +20,7 @@ class CArray::Inspector # :nodoc:
|
|
|
15
20
|
class_name = get_class_name()
|
|
16
21
|
type_name = get_type_name()
|
|
17
22
|
shape = get_shape()
|
|
18
|
-
data_spec = get_data_spec(0, Array.new(@carray.ndim){0}, formatter)
|
|
23
|
+
data_spec = get_data_spec(0, Array.new(@carray.ndim){0}, formatter, abbrev)
|
|
19
24
|
info_list = get_info_list()
|
|
20
25
|
output = ["<",
|
|
21
26
|
format("%s.%s(%s)", class_name, type_name, shape.join(",")),
|
|
@@ -146,7 +151,7 @@ class CArray::Inspector # :nodoc:
|
|
|
146
151
|
end
|
|
147
152
|
end
|
|
148
153
|
|
|
149
|
-
def get_data_spec (level, idx, formatter)
|
|
154
|
+
def get_data_spec (level, idx, formatter, abbrev = true)
|
|
150
155
|
io = +"[ " # mutable buffer; `<<` below appends into it
|
|
151
156
|
ndim = @carray.ndim
|
|
152
157
|
dim = @carray.shape
|
|
@@ -163,7 +168,7 @@ class CArray::Inspector # :nodoc:
|
|
|
163
168
|
if i != dim[level] - 1
|
|
164
169
|
io << ", "
|
|
165
170
|
end
|
|
166
|
-
if io.length > 48 - 2*level
|
|
171
|
+
if abbrev and io.length > 48 - 2*level
|
|
167
172
|
if i < dim[level] - 1
|
|
168
173
|
io << "..."
|
|
169
174
|
over = true
|
|
@@ -182,24 +187,24 @@ class CArray::Inspector # :nodoc:
|
|
|
182
187
|
end
|
|
183
188
|
else
|
|
184
189
|
over = false
|
|
185
|
-
show = [dim[level], 5].min
|
|
190
|
+
show = abbrev ? [dim[level], 5].min : dim[level]
|
|
186
191
|
show.times do |i|
|
|
187
192
|
idx[level] = i
|
|
188
|
-
io << get_data_spec(level+1, idx, formatter)
|
|
193
|
+
io << get_data_spec(level+1, idx, formatter, abbrev)
|
|
189
194
|
if i < show - 1
|
|
190
195
|
io << ",\n" + " " * (level+1)
|
|
191
196
|
end
|
|
192
|
-
if i >= 2 and dim[level] > 5
|
|
197
|
+
if abbrev and i >= 2 and dim[level] > 5
|
|
193
198
|
break
|
|
194
199
|
end
|
|
195
200
|
end
|
|
196
|
-
if dim[level] > 5
|
|
201
|
+
if abbrev and dim[level] > 5
|
|
197
202
|
io << "... ... ..."
|
|
198
203
|
over = true
|
|
199
204
|
end
|
|
200
205
|
if over
|
|
201
206
|
idx[level] = dim[level] - 1
|
|
202
|
-
io << "\n"+ " " * (level+1) + get_data_spec(level+1, idx, formatter)
|
|
207
|
+
io << "\n"+ " " * (level+1) + get_data_spec(level+1, idx, formatter, abbrev)
|
|
203
208
|
end
|
|
204
209
|
end
|
|
205
210
|
io << " ]"
|
|
@@ -219,6 +224,34 @@ class CArray
|
|
|
219
224
|
return CArray::Inspector.new(self).inspect_string
|
|
220
225
|
end
|
|
221
226
|
|
|
227
|
+
# @overload inspect_full
|
|
228
|
+
# The same description as {#inspect}, with every element rendered
|
|
229
|
+
# instead of the `...` preview.
|
|
230
|
+
#
|
|
231
|
+
# `inspect` abbreviates on purpose -- it is what `p`, `irb` and an
|
|
232
|
+
# error message call, and a million-cell array has to stay readable
|
|
233
|
+
# there. `inspect_full` is for the other moment, when the whole
|
|
234
|
+
# array is the thing you came to look at:
|
|
235
|
+
#
|
|
236
|
+
# puts a.inspect_full
|
|
237
|
+
#
|
|
238
|
+
# The header, the layout and the `_` for a masked cell are
|
|
239
|
+
# `inspect`'s; only the eliding is dropped, so for an array small
|
|
240
|
+
# enough that `inspect` was not abbreviating anything the two give
|
|
241
|
+
# the same string.
|
|
242
|
+
#
|
|
243
|
+
# Note that neither of these is `to_s`, which returns the **raw
|
|
244
|
+
# bytes** of the storage rather than anything printable.
|
|
245
|
+
#
|
|
246
|
+
# The result is one String holding every element, so it is as large
|
|
247
|
+
# as the array is: nothing here is streamed, and a line is as long
|
|
248
|
+
# as the last axis makes it.
|
|
249
|
+
#
|
|
250
|
+
# @return [String]
|
|
251
|
+
def inspect_full
|
|
252
|
+
return CArray::Inspector.new(self).inspect_string(abbrev: false)
|
|
253
|
+
end
|
|
254
|
+
|
|
222
255
|
private
|
|
223
256
|
|
|
224
257
|
def desc
|
data/lib/carray/iterator.rb
CHANGED
|
@@ -36,6 +36,114 @@ class CAIterator
|
|
|
36
36
|
# rather than reading as "no such method" -- that gap is the member's to close.
|
|
37
37
|
# A member that genuinely cannot provide one overrides it to raise with its
|
|
38
38
|
# own reason.
|
|
39
|
+
# The methods below are declared here, so that the family surface is
|
|
40
|
+
# documented in one place; each member generates its own implementation and
|
|
41
|
+
# a member for which one is ill-defined overrides it to raise with a reason.
|
|
42
|
+
# "Piece" is the unit a member iterates over: a slab, a window, a block, a
|
|
43
|
+
# category, or a group.
|
|
44
|
+
#
|
|
45
|
+
# @!method sum
|
|
46
|
+
# Returns the sum of each piece.
|
|
47
|
+
# @return [CArray] one value per piece, shaped {#shape}.
|
|
48
|
+
# @!method accumulate
|
|
49
|
+
# Returns the sum of each piece kept in the source's own `data_type`,
|
|
50
|
+
# wrapping at its width, where {#sum} answers in the type the core
|
|
51
|
+
# promotes to (`:float64` for integers).
|
|
52
|
+
# @return [CArray] one value per piece.
|
|
53
|
+
# @!method prod
|
|
54
|
+
# Returns the product of each piece.
|
|
55
|
+
# @return [CArray] one value per piece.
|
|
56
|
+
# @!method mean
|
|
57
|
+
# Returns the arithmetic mean of each piece.
|
|
58
|
+
# @return [CArray] one value per piece.
|
|
59
|
+
# @!method min
|
|
60
|
+
# Returns the smallest value in each piece.
|
|
61
|
+
# @return [CArray] one value per piece.
|
|
62
|
+
# @!method max
|
|
63
|
+
# Returns the largest value in each piece.
|
|
64
|
+
# @return [CArray] one value per piece.
|
|
65
|
+
# @!method minmax
|
|
66
|
+
# Returns the smallest and largest value of each piece, found in one pass.
|
|
67
|
+
# @return [Array<CArray>] the pair `[min, max]`.
|
|
68
|
+
# @!method variance
|
|
69
|
+
# Returns the sample variance (divisor `n - 1`) of each piece.
|
|
70
|
+
# @return [CArray] one value per piece.
|
|
71
|
+
# @!method variancep
|
|
72
|
+
# Returns the population variance (divisor `n`) of each piece.
|
|
73
|
+
# @return [CArray] one value per piece.
|
|
74
|
+
# @!method stddev
|
|
75
|
+
# Returns the sample standard deviation (divisor `n - 1`) of each piece.
|
|
76
|
+
# @return [CArray] one value per piece.
|
|
77
|
+
# @!method stddevp
|
|
78
|
+
# Returns the population standard deviation (divisor `n`) of each piece.
|
|
79
|
+
# @return [CArray] one value per piece.
|
|
80
|
+
# @!method all
|
|
81
|
+
# Returns whether every cell of each piece is true.
|
|
82
|
+
# @return [CArray] `:boolean`, one value per piece.
|
|
83
|
+
# @!method any
|
|
84
|
+
# Returns whether any cell of each piece is true.
|
|
85
|
+
# @return [CArray] `:boolean`, one value per piece.
|
|
86
|
+
# @!method min_index
|
|
87
|
+
# Returns the position of the smallest value **within** each piece.
|
|
88
|
+
# @return [CArray] one index per piece.
|
|
89
|
+
# @!method max_index
|
|
90
|
+
# Returns the position of the largest value within each piece.
|
|
91
|
+
# @return [CArray] one index per piece.
|
|
92
|
+
# @!method min_addr
|
|
93
|
+
# Returns the flat address **in the source** of the smallest value of each
|
|
94
|
+
# piece -- which source cell holds it, rather than where it sits inside the
|
|
95
|
+
# piece. Use it to read the same cell out of another source-shaped array.
|
|
96
|
+
# @return [CArray] one flat address per piece.
|
|
97
|
+
# @!method max_addr
|
|
98
|
+
# Returns the flat address in the source of the largest value of each piece.
|
|
99
|
+
# @return [CArray] one flat address per piece.
|
|
100
|
+
# @!method wsum(weights)
|
|
101
|
+
# Returns the weighted sum of each piece.
|
|
102
|
+
# @param weights [CArray] one weight per source cell, shaped like the source.
|
|
103
|
+
# @return [CArray] one value per piece.
|
|
104
|
+
# @!method wmean(weights)
|
|
105
|
+
# Returns the weighted mean of each piece.
|
|
106
|
+
# @param weights [CArray] one weight per source cell, shaped like the source.
|
|
107
|
+
# @return [CArray] one value per piece.
|
|
108
|
+
# @!method median
|
|
109
|
+
# Returns the median of each piece.
|
|
110
|
+
# @return [CArray] one value per piece.
|
|
111
|
+
# @!method percentile(*pers)
|
|
112
|
+
# Returns the requested percentile(s) of each piece.
|
|
113
|
+
# @param pers [Array<Numeric>] percentile positions in `0..100`.
|
|
114
|
+
# @return [CArray, Array<CArray>] one array per requested position; a
|
|
115
|
+
# single position returns that array directly.
|
|
116
|
+
# @!method quantile
|
|
117
|
+
# Returns the five-number summary of each piece,
|
|
118
|
+
# `[min, Q1, median, Q3, max]`.
|
|
119
|
+
# @return [Array<CArray>] five arrays, one per position.
|
|
120
|
+
# @!method count(value = nil)
|
|
121
|
+
# Returns a count per piece: with no argument the cells that are not
|
|
122
|
+
# masked, with `UNDEF` the masked cells, and with any other value the
|
|
123
|
+
# cells equal to it.
|
|
124
|
+
# @param value [Object] value to match, or `UNDEF`.
|
|
125
|
+
# @return [CArray] one count per piece.
|
|
126
|
+
# @!method count_not_masked
|
|
127
|
+
# Returns the number of cells of each piece that are not masked.
|
|
128
|
+
# @return [CArray] one count per piece.
|
|
129
|
+
# @!method count_masked
|
|
130
|
+
# Returns the number of masked cells of each piece.
|
|
131
|
+
# @return [CArray] one count per piece.
|
|
132
|
+
# @!method elements
|
|
133
|
+
# Returns the total number of cells in each piece, masked or not.
|
|
134
|
+
# @return [CArray] one count per piece.
|
|
135
|
+
# @!method each
|
|
136
|
+
# Yields each piece in turn as a CArray.
|
|
137
|
+
# @yieldparam piece [CArray]
|
|
138
|
+
# @return [Enumerator, self] an Enumerator when no block is given.
|
|
139
|
+
# @!method reduce(init = nil)
|
|
140
|
+
# Folds the pieces with a block, for a reduction the family does not name.
|
|
141
|
+
# Without `init` the first piece seeds the accumulator.
|
|
142
|
+
# @param init [Object] initial accumulator value.
|
|
143
|
+
# @yieldparam acc [Object] running accumulator.
|
|
144
|
+
# @yieldparam piece [CArray] next piece.
|
|
145
|
+
# @yieldreturn [Object] updated accumulator.
|
|
146
|
+
# @return [Object] the final accumulator.
|
|
39
147
|
[
|
|
40
148
|
:sum, :accumulate, :prod, :mean, :min, :max, # tier 1
|
|
41
149
|
:variance, :stddev, :all, :any,
|
|
@@ -66,6 +174,41 @@ class CAIterator
|
|
|
66
174
|
# sort_addr. Un-overridden each is simply unavailable, not a contract
|
|
67
175
|
# violation. min_addr / max_addr stay required: a single winner address is
|
|
68
176
|
# well-defined even for an overlapping window.
|
|
177
|
+
# @!method map
|
|
178
|
+
# Returns a source-shaped array built by applying the block to each piece
|
|
179
|
+
# and scattering the result back into that piece's cells.
|
|
180
|
+
# @yieldparam piece [CArray]
|
|
181
|
+
# @yieldreturn [CArray, Numeric] replacement values for the piece.
|
|
182
|
+
# @return [CArray] shaped like the source.
|
|
183
|
+
# @raise [NotImplementedError] for a member whose pieces overlap, where a
|
|
184
|
+
# cell would receive more than one value.
|
|
185
|
+
# @!method sort_addr
|
|
186
|
+
# Returns a source-shaped array whose cells, read piece by piece, give the
|
|
187
|
+
# flat source addresses that put that piece in ascending order.
|
|
188
|
+
# @return [CArray] `:int64`, shaped like the source.
|
|
189
|
+
# @raise [NotImplementedError] for a member whose pieces overlap.
|
|
190
|
+
# @!method cumsum
|
|
191
|
+
# Returns a source-shaped array of the running sum within each piece.
|
|
192
|
+
# @return [CArray] shaped like the source.
|
|
193
|
+
# @raise [NotImplementedError] for a member whose pieces overlap, where a
|
|
194
|
+
# cell has no single running value.
|
|
195
|
+
# @!method cumprod
|
|
196
|
+
# Returns a source-shaped array of the running product within each piece.
|
|
197
|
+
# @return [CArray] shaped like the source.
|
|
198
|
+
# @raise [NotImplementedError] for a member whose pieces overlap.
|
|
199
|
+
# @!method cummax
|
|
200
|
+
# Returns a source-shaped array of the running maximum within each piece.
|
|
201
|
+
# @return [CArray] shaped like the source.
|
|
202
|
+
# @raise [NotImplementedError] for a member whose pieces overlap.
|
|
203
|
+
# @!method cummin
|
|
204
|
+
# Returns a source-shaped array of the running minimum within each piece.
|
|
205
|
+
# @return [CArray] shaped like the source.
|
|
206
|
+
# @raise [NotImplementedError] for a member whose pieces overlap.
|
|
207
|
+
# @!method cumcount
|
|
208
|
+
# Returns a source-shaped array of the running count of cells that are not
|
|
209
|
+
# masked within each piece.
|
|
210
|
+
# @return [CArray] shaped like the source.
|
|
211
|
+
# @raise [NotImplementedError] for a member whose pieces overlap.
|
|
69
212
|
[:map, :sort_addr,
|
|
70
213
|
:cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |name|
|
|
71
214
|
define_method(name) do |*, **, &_blk|
|
data/lib/carray/lazy.rb
CHANGED
|
@@ -901,41 +901,4 @@ class << CArray
|
|
|
901
901
|
end
|
|
902
902
|
CArray::FuseSource.evaluate(block)
|
|
903
903
|
end
|
|
904
|
-
|
|
905
|
-
# @overload jit_for (*extents) { |i, j, ...| ... }
|
|
906
|
-
# Runs a block over an index space, so that a cell may reach the ones
|
|
907
|
-
# around it -- a recurrence, a stencil. The block is compiled, and the
|
|
908
|
-
# compiler is the carray-jit gem.
|
|
909
|
-
# @raise [NotImplementedError] when that gem is not installed.
|
|
910
|
-
def jit_for (*extents, **options)
|
|
911
|
-
raise NotImplementedError, no_compiler("jit_for")
|
|
912
|
-
end
|
|
913
|
-
|
|
914
|
-
# @overload jit_each { ... }
|
|
915
|
-
# Runs a block over the cells of arrays at once, naming each cell by the
|
|
916
|
-
# array it came from, and writing what it computes into arrays of yours:
|
|
917
|
-
# `CArray.jit_each { out = a + b * c }`. The block is compiled, and the
|
|
918
|
-
# compiler is the carray-jit gem.
|
|
919
|
-
# @raise [NotImplementedError] when that gem is not installed.
|
|
920
|
-
def jit_each (&block)
|
|
921
|
-
raise NotImplementedError, no_compiler("jit_each")
|
|
922
|
-
end
|
|
923
|
-
|
|
924
|
-
# @overload jit_map { ... }
|
|
925
|
-
# The same block with its value asked for: the last statement is what
|
|
926
|
-
# every cell of the result gets, and the result comes back. The block is
|
|
927
|
-
# compiled, and the compiler is the carray-jit gem.
|
|
928
|
-
# @raise [NotImplementedError] when that gem is not installed.
|
|
929
|
-
def jit_map (&block)
|
|
930
|
-
raise NotImplementedError, no_compiler("jit_map")
|
|
931
|
-
end
|
|
932
|
-
|
|
933
|
-
private
|
|
934
|
-
|
|
935
|
-
def no_compiler (name)
|
|
936
|
-
"CArray.#{name} compiles its block, and the compiler is the carray-jit " \
|
|
937
|
-
"gem, which is not installed. An expression over whole arrays can be " \
|
|
938
|
-
"written as `CArray.fuse { ... }` instead, which needs no compiler; " \
|
|
939
|
-
"reaching a neighbouring cell, or writing the loop itself, does."
|
|
940
|
-
end
|
|
941
904
|
end
|
data/lib/carray/mask_gap_fill.rb
CHANGED
|
@@ -172,7 +172,9 @@ class CArray
|
|
|
172
172
|
return vec[present].linear_fetch(addr)
|
|
173
173
|
end
|
|
174
174
|
vval = vec.value.float64[present] # valid values
|
|
175
|
-
|
|
175
|
+
# Mark the out-of-range NaN before casting back: for an integer data_type
|
|
176
|
+
# the cast turns NaN into 0, and mask_invalid then has nothing left to find.
|
|
177
|
+
vval.linear_fetch(addr).mask_invalid.to_type(vec.data_type)
|
|
176
178
|
end
|
|
177
179
|
|
|
178
180
|
# Yield an index key (Array with `nil` at `axis`, integers elsewhere)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# The value-hash discovery family (`unique`, `nunique`,
|
|
4
|
+
# `mask_duplicates`, ...) interns one **cell** at a time. `along:`
|
|
5
|
+
# widens the unit to a whole sub-array: the sub-arrays enumerated
|
|
6
|
+
# along one axis, each compared as a single value.
|
|
7
|
+
#
|
|
8
|
+
# Nothing new has to hash. `fz_hash`'s third key lane already interns
|
|
9
|
+
# a fixed-width block of bytes (FNV-1a, with a memcmp re-check), which
|
|
10
|
+
# is what a sub-array is once it sits contiguously; this method is the
|
|
11
|
+
# bridge to it, and the three things the bridge has to get right:
|
|
12
|
+
#
|
|
13
|
+
# - the sub-arrays have to be contiguous before their bytes mean
|
|
14
|
+
# anything, which a transpose or a strided view does not give;
|
|
15
|
+
# - byte equality is not the family's contract for floats, where
|
|
16
|
+
# every NaN is one value and -0.0 is +0.0;
|
|
17
|
+
# - masks fold for free -- a CARefer that covers several parent
|
|
18
|
+
# cells with one view cell OR-reduces their mask bits, so a
|
|
19
|
+
# sub-array holding a masked cell is a masked sub-array and the
|
|
20
|
+
# kernels skip it.
|
|
21
|
+
#
|
|
22
|
+
# Returns a 1-D fixlen array, one cell per sub-array, for the caller
|
|
23
|
+
# to run an ordinary cell-level discovery method over.
|
|
24
|
+
private def fibers_as_cells (along, caller_name)
|
|
25
|
+
if ndim < 2
|
|
26
|
+
raise ArgumentError,
|
|
27
|
+
"#{caller_name}: along: compares sub-arrays, and a #{ndim}-D array " \
|
|
28
|
+
"has none -- drop along: for the whole-array form"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if data_type == CA_OBJECT
|
|
32
|
+
raise ArgumentError,
|
|
33
|
+
"#{caller_name}: along: is not available for an object array -- " \
|
|
34
|
+
"its cells hold Ruby references, which would compare by identity " \
|
|
35
|
+
"rather than by value"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
axis = normalize_axis(along, caller_name)
|
|
39
|
+
|
|
40
|
+
# Bring the named axis to the front so each sub-array is one
|
|
41
|
+
# unbroken run of bytes, and take a copy: `refer` reinterprets a
|
|
42
|
+
# byte buffer, and a view whose element stride is not its element
|
|
43
|
+
# width is not one. The copy is also what makes the normalisation
|
|
44
|
+
# below safe to write.
|
|
45
|
+
order = [axis] + (0...ndim).to_a.reject { |i| i == axis }
|
|
46
|
+
block = (axis.zero? ? self : transpose(*order)).copy
|
|
47
|
+
|
|
48
|
+
# Two values the family calls equal are not equal byte for byte.
|
|
49
|
+
# Normalise them so the bytes say what the values mean.
|
|
50
|
+
if block.float?
|
|
51
|
+
block[:is_nan] = Float::NAN # every NaN is one value
|
|
52
|
+
block[:eq, 0.0] = 0.0 # -0.0 is +0.0
|
|
53
|
+
elsif block.complex?
|
|
54
|
+
[block.real, block.imag].each do |part|
|
|
55
|
+
part[:is_nan] = Float::NAN
|
|
56
|
+
part[:eq, 0.0] = 0.0
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
count = shape[axis]
|
|
61
|
+
block.refer(:fixlen, [count], bytes: elements / count * bytes)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# `axis:` and `along:` ask different questions of the same array, so
|
|
65
|
+
# answering both at once has no meaning.
|
|
66
|
+
private def reject_axis_with_along (axis, along, caller_name)
|
|
67
|
+
return unless axis and along
|
|
68
|
+
raise ArgumentError,
|
|
69
|
+
"#{caller_name}: axis: and along: cannot be given together -- " \
|
|
70
|
+
"axis: is about the values inside each sub-array, along: is about " \
|
|
71
|
+
"the sub-arrays themselves"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload factorize
|
|
4
|
+
# Returns `[codes, levels]` in one pass: `levels` is a 1-D CArray of
|
|
5
|
+
# the distinct values of `self` in first-appearance order — what
|
|
6
|
+
# {#unique} answers — and `codes` is an integer CArray of `self`'s
|
|
7
|
+
# shape where `levels[codes[i]]` is `self[i]`.
|
|
8
|
+
#
|
|
9
|
+
# This is the member of the value-hash discovery family ({#unique},
|
|
10
|
+
# {#value_counts}, {#mask_duplicates}, {#nunique}) that hands back
|
|
11
|
+
# the codes as storage. {#unique} answers with the vocabulary alone
|
|
12
|
+
# and {#categorize} wraps both in a {CACategorical} Face; a caller
|
|
13
|
+
# who wants the codes themselves — a position to scatter into, a key
|
|
14
|
+
# to group by, a dense renumbering of sparse keys — would otherwise
|
|
15
|
+
# pay a second pass or take the Face and its Ruby label list.
|
|
16
|
+
#
|
|
17
|
+
# `codes` takes the narrowest unsigned data type the vocabulary
|
|
18
|
+
# fits, reserving that type's top value as the exclusion sentinel.
|
|
19
|
+
# A cell that joins no category — a masked cell — is both masked and
|
|
20
|
+
# holds the sentinel, exactly as {CACategorical}'s storage is, so a
|
|
21
|
+
# consumer may read either.
|
|
22
|
+
#
|
|
23
|
+
# Distinctness is the family's hash-key judgement (see {#unique}):
|
|
24
|
+
# `==` for numeric with all NaN collapsed to one value and
|
|
25
|
+
# -0.0 / +0.0 the same value; `eql?` / `hash` for `CA_OBJECT` and
|
|
26
|
+
# `CA_FIXLEN`. Complex is not a lane the factorizer takes and raises
|
|
27
|
+
# {CArray::DataTypeError}.
|
|
28
|
+
#
|
|
29
|
+
# There is no `sort:` here, unlike {#unique} and {#value_counts}:
|
|
30
|
+
# the codes index the levels, so reordering the vocabulary would
|
|
31
|
+
# desync them. Take {#unique}`(sort: true)` where the codes are not
|
|
32
|
+
# wanted, or sort afterwards and carry the codes through the same
|
|
33
|
+
# permutation.
|
|
34
|
+
#
|
|
35
|
+
# @return [Array(CArray, CArray)] `[codes, levels]`.
|
|
36
|
+
def factorize
|
|
37
|
+
# One linear pass through the shared value hash (C
|
|
38
|
+
# __factorize_appearance__). It writes the sentinel into the
|
|
39
|
+
# excluded cells but leaves the codes unmasked; masking them is what
|
|
40
|
+
# CACategorical.from_codes does on its way to the Face, and this
|
|
41
|
+
# surface owes the same, since it hands the storage out bare.
|
|
42
|
+
codes, levels = __factorize_appearance__
|
|
43
|
+
excluded = codes.eq(CACategorical::SENTINEL[codes.data_type])
|
|
44
|
+
if excluded.count(true) > 0
|
|
45
|
+
codes.mask = codes.has_mask? ? (codes.mask | excluded) : excluded
|
|
46
|
+
end
|
|
47
|
+
[codes, levels]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|