carray 3.0.5 → 3.0.6
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 +233 -0
- data/README.md +1 -1
- data/carray.gemspec +3 -2
- data/ext/ca_axis_dispatch.c +6 -0
- data/ext/ca_axis_group.c +1 -1
- data/ext/ca_categorical_iterator.c +7 -7
- data/ext/ca_kernel_iterator.c +116 -231
- data/ext/ca_kernel_iterator.h +410 -254
- data/ext/ca_obj_bincmp.c +9 -1
- data/ext/ca_obj_binop.c +10 -1
- data/ext/ca_obj_bitfield.c +2 -0
- data/ext/ca_obj_byte_swap.c +2 -0
- data/ext/ca_obj_face.c +2 -0
- data/ext/ca_obj_fake.c +3 -1
- data/ext/ca_obj_grid.c +167 -12
- data/ext/ca_obj_meld.c +69 -113
- data/ext/ca_obj_moncmp.c +9 -1
- data/ext/ca_obj_monop.c +40 -2
- data/ext/ca_obj_object.c +54 -20
- data/ext/ca_obj_reduce.c +15 -1
- data/ext/ca_obj_refer.c +65 -20
- data/ext/ca_obj_remap.c +69 -10
- data/ext/ca_obj_roll.c +1 -0
- data/ext/ca_obj_select.c +4 -1
- data/ext/ca_obj_select_axis.c +1 -0
- data/ext/ca_obj_shift.c +1 -0
- data/ext/ca_obj_stack.c +310 -111
- data/ext/ca_obj_stride.c +131 -45
- data/ext/ca_obj_string.c +18 -2
- data/ext/ca_obj_tile.c +1 -0
- data/ext/ca_obj_triop.c +9 -1
- data/ext/ca_obj_window.c +1 -0
- data/ext/ca_op_ipower.c +8 -8
- data/ext/caframe_csv_reader.c +1 -1
- data/ext/carray.h +2 -1
- data/ext/carray_access.c +27 -64
- data/ext/carray_address_basis.c +134 -0
- data/ext/carray_attribute.c +8 -0
- data/ext/carray_broadcast.c +3 -4
- data/ext/carray_build_flags.h +1 -1
- data/ext/carray_cast.c +8 -11
- data/ext/carray_conversion.c +9 -5
- data/ext/carray_core.c +811 -72
- data/ext/carray_element.c +13 -9
- data/ext/carray_histogram.c +25 -3
- data/ext/carray_internal.h +147 -12
- data/ext/carray_kernels_bincmp.c +2007 -3
- data/ext/carray_kernels_binop.c +25365 -5199
- data/ext/carray_kernels_init.c +8 -8
- data/ext/carray_kernels_monop.c +10696 -3678
- data/ext/carray_kernels_reduce_aggregate.c +4429 -702
- data/ext/carray_kernels_reduce_boolean.c +1533 -15
- data/ext/carray_kernels_reduce_cumulative.c +1808 -248
- data/ext/carray_kernels_reduce_extreme.c +4508 -511
- data/ext/carray_kernels_reduce_variance.c +2284 -152
- data/ext/carray_kernels_scan.c +240 -240
- data/ext/carray_kernels_search.c +4211 -264
- data/ext/carray_kernels_sort.c +367 -450
- data/ext/carray_lazy.c +47 -0
- data/ext/carray_loop.c +1 -1
- data/ext/carray_mask.c +130 -139
- data/ext/carray_median_percentile.c +4 -4
- data/ext/carray_memory_view.c +13 -7
- data/ext/carray_operator.c +147 -44
- data/ext/carray_order.c +3 -2
- data/ext/carray_random.c +4 -1
- data/ext/carray_slab.c +97 -21
- data/ext/carray_sort.c +1 -1
- data/ext/carray_test.c +74 -19
- data/ext/carray_time_text.c +2 -2
- data/ext/extconf.rb +11 -0
- data/ext/mkkernel.rb +346 -94
- data/ext/ruby_carray.c +4 -1
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +25 -1
- data/lib/carray/autoload_carray.rb +3 -3
- data/lib/carray/autoload_method_extension.rb +5 -3
- data/lib/carray/axis_group.rb +13 -7
- data/lib/carray/basics.rb +1 -1
- data/lib/carray/block_iterator.rb +17 -3
- data/lib/carray/categorical.rb +6 -8
- data/lib/carray/categorical_iterator.rb +2 -2
- data/lib/carray/complex.rb +2 -2
- data/lib/carray/conditional.rb +3 -2
- data/lib/carray/frame/column_verbs.rb +45 -14
- data/lib/carray/frame/convert.rb +19 -0
- data/lib/carray/frame/csv_parser.rb +32 -32
- data/lib/carray/frame/describe.rb +4 -2
- data/lib/carray/frame/frame.rb +399 -78
- data/lib/carray/frame/group.rb +679 -53
- data/lib/carray/frame/io.rb +209 -71
- data/lib/carray/frame/join.rb +146 -37
- data/lib/carray/frame/records.rb +3 -3
- data/lib/carray/frame/reshape.rb +89 -11
- data/lib/carray/frame/sort.rb +57 -9
- data/lib/carray/frame/verbs.rb +384 -49
- data/lib/carray/frame/windows.rb +44 -6
- data/lib/carray/inspect.rb +7 -2
- data/lib/carray/iterator.rb +32 -0
- data/lib/carray/lazy.rb +17 -8
- data/lib/carray/mask_gap_fill.rb +37 -17
- data/lib/carray/meld_reduce.rb +1 -1
- data/lib/carray/methods/choose.rb +3 -2
- data/lib/carray/methods/meshgrid.rb +3 -1
- data/lib/carray/runtime.rb +47 -8
- data/lib/carray/segment_iterator.rb +23 -8
- data/lib/carray/serialize.rb +34 -17
- data/lib/carray/stack.rb +18 -6
- data/lib/carray/time.rb +5 -0
- data/lib/carray/window_iterator.rb +16 -4
- data/yard-stubs/ca_obj_array.rb +26 -1
- data/yard-stubs/carray_cast.rb +8 -5
- data/yard-stubs/carray_conversion.rb +8 -2
- data/yard-stubs/carray_mask.rb +20 -1
- data/yard-stubs/carray_math.rb +31 -16
- data/yard-stubs/carray_memory_view.rb +7 -0
- data/yard-stubs/carray_test.rb +7 -4
- metadata +18 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# A small, self-contained CSV tokenizer for CAFrame.
|
|
1
|
+
# A small, self-contained CSV tokenizer for CAFrame.read_csv.
|
|
2
2
|
#
|
|
3
3
|
# It produces raw String cells and does no type inference — casting is a
|
|
4
4
|
# separate step. The tokenizer is tuned for the
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
# Two entry points share the Tokenizer:
|
|
23
23
|
# * CSVParser.parse / parse_file -- whole-file parse into [headers, rows].
|
|
24
24
|
# * CSVReader -- the block reading-control DSL used by
|
|
25
|
-
# CAFrame.
|
|
25
|
+
# CAFrame.read_csv (header / skip / body /
|
|
26
26
|
# column_names).
|
|
27
27
|
|
|
28
28
|
require "strscan"
|
|
29
29
|
require "stringio"
|
|
30
30
|
|
|
31
31
|
class CAFrame
|
|
32
|
-
# CSV tokenizer behind `CAFrame.
|
|
32
|
+
# CSV tokenizer behind `CAFrame.read_csv`. It produces raw String cells
|
|
33
33
|
# and does no type inference — casting is a separate step.
|
|
34
34
|
#
|
|
35
35
|
# Records with no quote character take a `String#split` fast path; only
|
|
@@ -79,7 +79,7 @@ class CAFrame
|
|
|
79
79
|
|
|
80
80
|
# Parse an IO (or anything answering +gets+). The first record supplies the
|
|
81
81
|
# headers; the rest are data rows. Blank lines are skipped, including in a
|
|
82
|
-
# one-column file (
|
|
82
|
+
# one-column file (read_csv reads them there as missing cells).
|
|
83
83
|
def parse(io, sep: ",", quote: '"', strip: false)
|
|
84
84
|
tok = Tokenizer.new(sep, quote, strip)
|
|
85
85
|
headers = nil
|
|
@@ -315,24 +315,24 @@ class CAFrame
|
|
|
315
315
|
end
|
|
316
316
|
end
|
|
317
317
|
|
|
318
|
-
# The reading-control block for CAFrame.
|
|
318
|
+
# The reading-control block for CAFrame.read_csv. A file often has
|
|
319
319
|
# preamble lines, a units row, or no header at all; the block is given the
|
|
320
320
|
# reader and says, in order, how to consume the stream:
|
|
321
321
|
#
|
|
322
|
-
# CAFrame.
|
|
322
|
+
# CAFrame.read_csv(path) do |r|
|
|
323
323
|
# r.skip 3 # drop 3 preamble lines
|
|
324
324
|
# r.header # next record supplies the column names
|
|
325
325
|
# r.skip 1 # drop a units row
|
|
326
326
|
# r.data # the rest are data rows
|
|
327
327
|
# end
|
|
328
328
|
#
|
|
329
|
-
# CAFrame.
|
|
329
|
+
# CAFrame.read_csv(path) { it.column_names "date", "temp", "rh"; it.data }
|
|
330
330
|
#
|
|
331
331
|
# The verbs are +skip+ / +header+ / +column_names+ / +columns+ / +data+;
|
|
332
332
|
# each returns a value useful inline (header returns its fields) and the ordering is the
|
|
333
333
|
# script. The reader is a parameter, not self, so a local variable named
|
|
334
334
|
# +data+ or +header+ cannot stand in for the verb. Without a block,
|
|
335
|
-
#
|
|
335
|
+
# read_csv reads by its header: / data: / column_names: (see +layout+).
|
|
336
336
|
class CSVReader
|
|
337
337
|
def initialize(io, sep: ",", quote: '"', strip: false, read_text_as_const_string: false)
|
|
338
338
|
@read_text_as_const_string = read_text_as_const_string
|
|
@@ -388,7 +388,7 @@ class CAFrame
|
|
|
388
388
|
key = name.to_s
|
|
389
389
|
@named_headers ||= {}
|
|
390
390
|
if @named_headers.key?(key)
|
|
391
|
-
raise ArgumentError, "
|
|
391
|
+
raise ArgumentError, "read_csv: header(#{name.inspect}) is read twice"
|
|
392
392
|
end
|
|
393
393
|
@named_headers[key] = [fields, @tok.record_line, name]
|
|
394
394
|
end
|
|
@@ -403,7 +403,7 @@ class CAFrame
|
|
|
403
403
|
def column_names(*names)
|
|
404
404
|
names = names.flatten.map(&:to_s)
|
|
405
405
|
if names.include?("")
|
|
406
|
-
raise ArgumentError, "
|
|
406
|
+
raise ArgumentError, "read_csv: column_names: a column name can not be empty"
|
|
407
407
|
end
|
|
408
408
|
if @header_line
|
|
409
409
|
check_column_names(names, @names.size, @header_line)
|
|
@@ -431,15 +431,15 @@ class CAFrame
|
|
|
431
431
|
end
|
|
432
432
|
selection = selection.flatten
|
|
433
433
|
if selection.empty?
|
|
434
|
-
raise ArgumentError, "
|
|
434
|
+
raise ArgumentError, "read_csv: columns: names no column"
|
|
435
435
|
end
|
|
436
436
|
selection.each do |c|
|
|
437
437
|
next if c.is_a?(String)
|
|
438
|
-
CAFrame.__send__(:column_key, c, "
|
|
438
|
+
CAFrame.__send__(:column_key, c, "read_csv", "columns: a column name") if c.is_a?(Symbol)
|
|
439
439
|
next if c.is_a?(Integer) && c >= 0
|
|
440
440
|
next if c.is_a?(Range) && (c.begin.nil? || (c.begin.is_a?(Integer) && c.begin >= 0)) &&
|
|
441
441
|
(c.end.nil? || c.end.is_a?(Integer))
|
|
442
|
-
raise ArgumentError, "
|
|
442
|
+
raise ArgumentError, "read_csv: columns: takes column names, or indexes from 0 " \
|
|
443
443
|
"and Ranges of them (got #{c.inspect})"
|
|
444
444
|
end
|
|
445
445
|
@selection = selection
|
|
@@ -450,17 +450,17 @@ class CAFrame
|
|
|
450
450
|
private def column_renames(map)
|
|
451
451
|
map.each do |key, name|
|
|
452
452
|
if key.is_a?(Range)
|
|
453
|
-
raise ArgumentError, "
|
|
453
|
+
raise ArgumentError, "read_csv: columns: a Hash selects one column per key, " \
|
|
454
454
|
"not a Range (got #{key.inspect})"
|
|
455
455
|
end
|
|
456
|
-
CAFrame.__send__(:column_key, name, "
|
|
456
|
+
CAFrame.__send__(:column_key, name, "read_csv", "columns: a column name",
|
|
457
457
|
hint: "given for #{key.inspect}")
|
|
458
458
|
if name.empty?
|
|
459
|
-
raise ArgumentError, "
|
|
459
|
+
raise ArgumentError, "read_csv: columns: a column name can not be empty " \
|
|
460
460
|
"(given for #{key.inspect})"
|
|
461
461
|
end
|
|
462
462
|
end
|
|
463
|
-
CAFrame.__send__(:refuse_repeated_names, map.values, "
|
|
463
|
+
CAFrame.__send__(:refuse_repeated_names, map.values, "read_csv", "columns: names")
|
|
464
464
|
map.values
|
|
465
465
|
end
|
|
466
466
|
|
|
@@ -474,7 +474,7 @@ class CAFrame
|
|
|
474
474
|
# Without a header the columns are named c0, c1, ... by position.
|
|
475
475
|
j = names ? names.index(c) : c[/\Ac(0|[1-9]\d*)\z/, 1]&.to_i
|
|
476
476
|
if j.nil?
|
|
477
|
-
raise KeyError, "
|
|
477
|
+
raise KeyError, "read_csv: columns: names no column #{c.inspect}" +
|
|
478
478
|
(names ? "" : " (with no header the columns are c0, c1, ...)")
|
|
479
479
|
end
|
|
480
480
|
j
|
|
@@ -488,9 +488,9 @@ class CAFrame
|
|
|
488
488
|
(first..last).to_a
|
|
489
489
|
end
|
|
490
490
|
end
|
|
491
|
-
CAFrame.__send__(:refuse_repeated_names, indexes, "
|
|
491
|
+
CAFrame.__send__(:refuse_repeated_names, indexes, "read_csv", "columns: selects column")
|
|
492
492
|
if ncol && (bad = indexes.find { |j| j >= ncol })
|
|
493
|
-
raise ArgumentError, "
|
|
493
|
+
raise ArgumentError, "read_csv: columns: #{bad} is past the #{ncol} columns of the file"
|
|
494
494
|
end
|
|
495
495
|
indexes
|
|
496
496
|
end
|
|
@@ -503,7 +503,7 @@ class CAFrame
|
|
|
503
503
|
|
|
504
504
|
private def check_column_names(names, ncol, line)
|
|
505
505
|
return if names.size == ncol
|
|
506
|
-
raise ArgumentError, "
|
|
506
|
+
raise ArgumentError, "read_csv: column_names: gives #{names.size} " \
|
|
507
507
|
"name#{names.size == 1 ? '' : 's'} for the #{ncol} columns of line #{line}"
|
|
508
508
|
end
|
|
509
509
|
|
|
@@ -525,7 +525,7 @@ class CAFrame
|
|
|
525
525
|
read_data(nil)
|
|
526
526
|
end
|
|
527
527
|
|
|
528
|
-
# The header: / data: / column_names: of
|
|
528
|
+
# The header: / data: / column_names: of read_csv, as the verbs above.
|
|
529
529
|
# They index the lines of the file from 0, as File.readlines does (an
|
|
530
530
|
# error names a line from 1, as an editor does). header: defaults to 0,
|
|
531
531
|
# or to none (false) when column_names: is given; data: is the first line
|
|
@@ -534,12 +534,12 @@ class CAFrame
|
|
|
534
534
|
def layout(header: nil, data: nil, column_names: nil, columns: nil, attrs: nil)
|
|
535
535
|
header = column_names ? false : 0 if header.nil?
|
|
536
536
|
unless header == false || (header.is_a?(Integer) && header >= 0)
|
|
537
|
-
raise ArgumentError, "
|
|
537
|
+
raise ArgumentError, "read_csv: header: takes the index of a line, from 0, " \
|
|
538
538
|
"or false (got #{header.inspect})"
|
|
539
539
|
end
|
|
540
|
-
attr_keys = attrs.nil? ? [] : CAFrame.send(:csv_attr_keys, attrs, "
|
|
540
|
+
attr_keys = attrs.nil? ? [] : CAFrame.send(:csv_attr_keys, attrs, "read_csv")
|
|
541
541
|
if !header && !attr_keys.empty?
|
|
542
|
-
raise ArgumentError, "
|
|
542
|
+
raise ArgumentError, "read_csv: attrs: reads the lines below the header, " \
|
|
543
543
|
"so it needs a header line"
|
|
544
544
|
end
|
|
545
545
|
first, last = CSVReader.data_lines(data)
|
|
@@ -552,7 +552,7 @@ class CAFrame
|
|
|
552
552
|
self.columns(*(columns.is_a?(Array) ? columns : [columns])) if columns
|
|
553
553
|
first ||= @tok.lineno
|
|
554
554
|
if first < @tok.lineno
|
|
555
|
-
raise ArgumentError, "
|
|
555
|
+
raise ArgumentError, "read_csv: data: #{first} does not come after header: #{header}" +
|
|
556
556
|
(attr_keys.empty? ? "" : " and the #{attr_keys.size} attrs: lines")
|
|
557
557
|
end
|
|
558
558
|
skip(first - @tok.lineno)
|
|
@@ -570,15 +570,15 @@ class CAFrame
|
|
|
570
570
|
e -= 1 if e.is_a?(Integer) && spec.exclude_end?
|
|
571
571
|
[spec.begin, e]
|
|
572
572
|
else
|
|
573
|
-
raise ArgumentError, "
|
|
573
|
+
raise ArgumentError, "read_csv: data: takes the index of a line or a Range of " \
|
|
574
574
|
"them, from 0 (got #{spec.inspect})"
|
|
575
575
|
end
|
|
576
576
|
[first, last].each do |n|
|
|
577
577
|
next if n.nil? || (n.is_a?(Integer) && n >= 0)
|
|
578
|
-
raise ArgumentError, "
|
|
578
|
+
raise ArgumentError, "read_csv: data: lines are indexed from 0 (got #{spec.inspect})"
|
|
579
579
|
end
|
|
580
580
|
if first && last && last < first
|
|
581
|
-
raise ArgumentError, "
|
|
581
|
+
raise ArgumentError, "read_csv: data: #{spec.inspect} ends before it starts"
|
|
582
582
|
end
|
|
583
583
|
[first, last]
|
|
584
584
|
end
|
|
@@ -648,7 +648,7 @@ class CAFrame
|
|
|
648
648
|
end
|
|
649
649
|
|
|
650
650
|
# The columns read_body_into_const_string_columns made, as +result+
|
|
651
|
-
# hands them to CAFrame.
|
|
651
|
+
# hands them to CAFrame.read_csv.
|
|
652
652
|
ConstStringColumns = Struct.new(:columns)
|
|
653
653
|
|
|
654
654
|
private def body_in_c(last = nil, chunk_bytes = CHUNK_BYTES)
|
|
@@ -767,7 +767,7 @@ class CAFrame
|
|
|
767
767
|
attrs = indexes.map { {} }
|
|
768
768
|
@named_headers.each do |key, (fields, line, name)|
|
|
769
769
|
if fields.size > ncol
|
|
770
|
-
raise ArgumentError, "
|
|
770
|
+
raise ArgumentError, "read_csv: header(#{name.inspect}) on line #{line} " \
|
|
771
771
|
"has #{fields.size} fields for the #{ncol} columns"
|
|
772
772
|
end
|
|
773
773
|
indexes.each_with_index do |j, k|
|
|
@@ -784,7 +784,7 @@ class CAFrame
|
|
|
784
784
|
@names ? @names.size : ((@rows || []).map(&:size).max || 0)
|
|
785
785
|
end
|
|
786
786
|
|
|
787
|
-
# [names_or_nil, rows] for CAFrame.
|
|
787
|
+
# [names_or_nil, rows] for CAFrame.read_csv to build from. names is nil when
|
|
788
788
|
# neither header nor column_names ran (positional names are generated).
|
|
789
789
|
def result
|
|
790
790
|
return table_result if @table
|
|
@@ -30,10 +30,12 @@ class CAFrame
|
|
|
30
30
|
# column with no present cell has UNDEF statistics. The index is not a
|
|
31
31
|
# column and is not summarized.
|
|
32
32
|
#
|
|
33
|
-
# @param names [Array<String>] the columns to
|
|
33
|
+
# @param names [Array<String, Range, Regexp, Symbol>] the columns to
|
|
34
|
+
# summarize, picked as +select+ picks them (a Symbol is a kind of column,
|
|
35
|
+
# such as :numeric); default all.
|
|
34
36
|
# @return [CAFrame] a new frame with one row per column.
|
|
35
37
|
def describe(*names)
|
|
36
|
-
names = names.empty? ? column_names :
|
|
38
|
+
names = names.empty? ? column_names : names_selected(names, "describe")
|
|
37
39
|
rows = names.map { |name| describe_column(self[name]) }
|
|
38
40
|
stats = %w[type count masked unique min max mean stddev]
|
|
39
41
|
cols = stats.to_h do |stat|
|