carray 3.0.4 → 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.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +438 -0
  3. data/README.md +1 -1
  4. data/carray.gemspec +3 -2
  5. data/ext/ca_array_pool.c +25 -1
  6. data/ext/ca_axis_dispatch.c +19 -20
  7. data/ext/ca_axis_group.c +1 -1
  8. data/ext/ca_categorical_iterator.c +34 -27
  9. data/ext/ca_compare.c +4 -4
  10. data/ext/ca_composite_dispatch.c +9 -9
  11. data/ext/ca_kernel_iterator.c +122 -237
  12. data/ext/ca_kernel_iterator.h +522 -337
  13. data/ext/ca_obj_array.c +69 -19
  14. data/ext/ca_obj_bincmp.c +10 -1
  15. data/ext/ca_obj_binop.c +11 -1
  16. data/ext/ca_obj_bitarray.c +5 -1
  17. data/ext/ca_obj_bitfield.c +60 -43
  18. data/ext/ca_obj_byte_swap.c +4 -1
  19. data/ext/ca_obj_const_string.c +15 -3
  20. data/ext/ca_obj_face.c +44 -1
  21. data/ext/ca_obj_fake.c +3 -1
  22. data/ext/ca_obj_field.c +13 -3
  23. data/ext/ca_obj_grid.c +219 -17
  24. data/ext/ca_obj_meld.c +69 -113
  25. data/ext/ca_obj_moncmp.c +9 -1
  26. data/ext/ca_obj_monop.c +86 -12
  27. data/ext/ca_obj_object.c +118 -32
  28. data/ext/ca_obj_record.c +3 -0
  29. data/ext/ca_obj_reduce.c +19 -1
  30. data/ext/ca_obj_refer.c +80 -26
  31. data/ext/ca_obj_remap.c +69 -10
  32. data/ext/ca_obj_roll.c +27 -6
  33. data/ext/ca_obj_select.c +38 -6
  34. data/ext/ca_obj_select_axis.c +1 -0
  35. data/ext/ca_obj_shift.c +32 -7
  36. data/ext/ca_obj_stack.c +311 -111
  37. data/ext/ca_obj_stride.c +159 -64
  38. data/ext/ca_obj_string.c +27 -2
  39. data/ext/ca_obj_tile.c +28 -7
  40. data/ext/ca_obj_transpose.c +8 -4
  41. data/ext/ca_obj_triop.c +12 -1
  42. data/ext/ca_obj_window.c +80 -50
  43. data/ext/ca_op_ipower.c +12 -12
  44. data/ext/ca_sort_kernels.h +6 -3
  45. data/ext/caframe_csv_reader.c +1 -1
  46. data/ext/carray.h +35 -1
  47. data/ext/carray_access.c +43 -67
  48. data/ext/carray_address_basis.c +134 -0
  49. data/ext/carray_attribute.c +32 -0
  50. data/ext/carray_broadcast.c +3 -4
  51. data/ext/carray_build_flags.h +1 -1
  52. data/ext/carray_cast.c +251 -93
  53. data/ext/carray_cast_func.rb +56 -26
  54. data/ext/carray_conversion.c +11 -7
  55. data/ext/carray_core.c +814 -72
  56. data/ext/carray_count.c +6 -0
  57. data/ext/carray_element.c +22 -13
  58. data/ext/carray_factorize.c +20 -20
  59. data/ext/carray_generate.c +8 -2
  60. data/ext/carray_histogram.c +26 -4
  61. data/ext/carray_index_classifier.c +4 -4
  62. data/ext/carray_internal.h +159 -12
  63. data/ext/carray_kernels_bincmp.c +2174 -170
  64. data/ext/carray_kernels_binop.c +25653 -5487
  65. data/ext/carray_kernels_init.c +8 -8
  66. data/ext/carray_kernels_moncmp.c +96 -96
  67. data/ext/carray_kernels_monop.c +12253 -5235
  68. data/ext/carray_kernels_reduce_aggregate.c +6150 -2423
  69. data/ext/carray_kernels_reduce_boolean.c +1533 -15
  70. data/ext/carray_kernels_reduce_cumulative.c +2442 -882
  71. data/ext/carray_kernels_reduce_extreme.c +5658 -1661
  72. data/ext/carray_kernels_reduce_variance.c +2534 -402
  73. data/ext/carray_kernels_scan.c +368 -368
  74. data/ext/carray_kernels_search.c +4551 -604
  75. data/ext/carray_kernels_sort.c +659 -751
  76. data/ext/carray_kernels_triop.c +114 -114
  77. data/ext/carray_key_windows.c +4 -0
  78. data/ext/carray_lazy.c +47 -0
  79. data/ext/carray_loop.c +1 -1
  80. data/ext/carray_mask.c +175 -161
  81. data/ext/carray_median_percentile.c +4 -4
  82. data/ext/carray_memory_view.c +15 -9
  83. data/ext/carray_operator.c +152 -49
  84. data/ext/carray_order.c +230 -10
  85. data/ext/carray_random.c +22 -19
  86. data/ext/carray_scatter.c +24 -7
  87. data/ext/carray_slab.c +128 -29
  88. data/ext/carray_sort.c +224 -16
  89. data/ext/carray_sort_kernel.c +5 -7
  90. data/ext/carray_struct.c +35 -43
  91. data/ext/carray_test.c +74 -19
  92. data/ext/carray_time_text.c +6 -4
  93. data/ext/carray_utils.c +63 -21
  94. data/ext/extconf.rb +11 -0
  95. data/ext/mkkernel.rb +546 -199
  96. data/ext/ruby_carray.c +4 -1
  97. data/ext/version.h +4 -4
  98. data/lib/carray/attribute.rb +42 -5
  99. data/lib/carray/autoload_carray.rb +3 -3
  100. data/lib/carray/autoload_method_extension.rb +5 -3
  101. data/lib/carray/axis_group.rb +13 -7
  102. data/lib/carray/basics.rb +28 -8
  103. data/lib/carray/block_iterator.rb +17 -3
  104. data/lib/carray/categorical.rb +35 -23
  105. data/lib/carray/categorical_iterator.rb +8 -8
  106. data/lib/carray/complex.rb +2 -2
  107. data/lib/carray/conditional.rb +3 -2
  108. data/lib/carray/const_string.rb +10 -0
  109. data/lib/carray/data_type_extension.rb +8 -1
  110. data/lib/carray/fixlen_string.rb +41 -2
  111. data/lib/carray/frame/column_verbs.rb +48 -16
  112. data/lib/carray/frame/concat.rb +17 -3
  113. data/lib/carray/frame/convert.rb +19 -0
  114. data/lib/carray/frame/csv_parser.rb +32 -32
  115. data/lib/carray/frame/describe.rb +4 -2
  116. data/lib/carray/frame/frame.rb +405 -85
  117. data/lib/carray/frame/group.rb +777 -64
  118. data/lib/carray/frame/io.rb +212 -72
  119. data/lib/carray/frame/join.rb +146 -37
  120. data/lib/carray/frame/records.rb +3 -3
  121. data/lib/carray/frame/reshape.rb +89 -11
  122. data/lib/carray/frame/sort.rb +57 -9
  123. data/lib/carray/frame/verbs.rb +384 -49
  124. data/lib/carray/frame/windows.rb +44 -6
  125. data/lib/carray/fuse_source.rb +31 -2
  126. data/lib/carray/inspect.rb +7 -2
  127. data/lib/carray/iterator.rb +32 -0
  128. data/lib/carray/lazy.rb +52 -8
  129. data/lib/carray/mask_gap_fill.rb +37 -17
  130. data/lib/carray/meld_reduce.rb +1 -1
  131. data/lib/carray/methods/choose.rb +3 -2
  132. data/lib/carray/methods/is_in.rb +8 -1
  133. data/lib/carray/methods/meshgrid.rb +3 -1
  134. data/lib/carray/runtime.rb +47 -8
  135. data/lib/carray/segment_iterator.rb +35 -16
  136. data/lib/carray/serialize.rb +53 -19
  137. data/lib/carray/stack.rb +33 -18
  138. data/lib/carray/string.rb +8 -0
  139. data/lib/carray/string_operation_extension.rb +13 -5
  140. data/lib/carray/struct_builder.rb +5 -3
  141. data/lib/carray/time.rb +112 -68
  142. data/lib/carray/window_iterator.rb +16 -4
  143. data/yard-stubs/ca_obj_array.rb +26 -1
  144. data/yard-stubs/ca_obj_transpose.rb +5 -4
  145. data/yard-stubs/carray_attribute.rb +3 -1
  146. data/yard-stubs/carray_cast.rb +10 -5
  147. data/yard-stubs/carray_conversion.rb +13 -3
  148. data/yard-stubs/carray_element.rb +7 -6
  149. data/yard-stubs/carray_mask.rb +28 -4
  150. data/yard-stubs/carray_math.rb +45 -20
  151. data/yard-stubs/carray_memory_view.rb +7 -0
  152. data/yard-stubs/carray_order.rb +100 -4
  153. data/yard-stubs/carray_scatter.rb +7 -5
  154. data/yard-stubs/carray_test.rb +7 -4
  155. metadata +18 -4
@@ -164,8 +164,15 @@ class CArray
164
164
  # midpoints go the same way as numpy's integer linspace).
165
165
  float_out = CArray.new(CA_FLOAT64, [n]).span(x1.to_f..x2.to_f)
166
166
  return float_out if data_type == CA_FLOAT64
167
+ # to_type masks a cell the type cannot hold; linspace refuses it
168
+ # instead, as arange does.
167
169
  # coerce: the type the caller asked for
168
- float_out.floor.to_type(data_type)
170
+ out = float_out.floor.to_type(data_type)
171
+ if out.any_masked?
172
+ raise RangeError,
173
+ "linspace: #{x1.inspect}..#{x2.inspect} is out of range for #{out.data_type_name}"
174
+ end
175
+ out
169
176
  end
170
177
 
171
178
  # @overload arange(stop)
@@ -32,9 +32,11 @@ class CArray
32
32
  # zero-copy, other string-bearing arrays materialise, numeric is rejected.
33
33
  # @overload fixlen_string(values, bytes: nil, truncate: :error)
34
34
  # @param values [Array<String, nil>] source values.
35
- # @param bytes [Integer, nil] slot width; defaults to the max bytesize.
35
+ # @param bytes [Integer, nil] slot width, 1 or more; defaults to the
36
+ # max bytesize (1 when every string is empty).
36
37
  # @param truncate [Symbol] `:error` or `:silent`.
37
38
  # @return [CAFixlenString]
39
+ # @raise [ArgumentError] when `bytes` is given below 1.
38
40
  # @overload fixlen_string(ca, bytes: nil, truncate: :error)
39
41
  # @param ca [CArray] a String Face, CA_OBJECT, or raw CA_FIXLEN array.
40
42
  # @return [CAFixlenString]
@@ -63,8 +65,11 @@ class CArray
63
65
  values = arg.to_a
64
66
  end
65
67
 
68
+ unless bytes.nil? || (bytes.is_a?(Integer) && bytes >= 1)
69
+ raise ArgumentError, "CArray.fixlen_string: invalid bytes: #{bytes.inspect} (a cell needs bytes: of 1 or more)"
70
+ end
66
71
  width = bytes || values.compact.map { |s| s.to_s.bytesize }.max || 1
67
- width = 1 if width < 1
72
+ width = 1 if width < 1 # every string empty: the narrowest cell
68
73
 
69
74
  if truncate == :error
70
75
  values.each_with_index do |s, i|
@@ -98,4 +103,38 @@ class CAFixlenString
98
103
 
99
104
  include CArray::StringOperationMixin
100
105
  include CArray::StringOperationMixin::Mutable
106
+
107
+ # ---- order family beyond the fixlen kernels --------------------------
108
+ #
109
+ # min / max / sort run on the cells' bytes. The members those kernels do
110
+ # not cover answer here: minmax as the pair of them, and the running
111
+ # extremes through the strings ({#to_string}), as CAString answers them,
112
+ # written back at this width.
113
+
114
+ # @return [Array(String, String), Array(CAFixlenString, CAFixlenString)]
115
+ # `[min, max]`, with the keywords of {CArray#min} / {CArray#max}.
116
+ def minmax (*args, **kw)
117
+ [min(*args, **kw), max(*args, **kw)]
118
+ end
119
+
120
+ # @return [CAFixlenString] the running maximum, as {CArray#cummax}.
121
+ def cummax (*args, **kw)
122
+ to_string.cummax(*args, **kw).to_fixlen_string(bytes: bytes)
123
+ end
124
+
125
+ # @return [CAFixlenString] the running minimum, as {CArray#cummin}.
126
+ def cummin (*args, **kw)
127
+ to_string.cummin(*args, **kw).to_fixlen_string(bytes: bytes)
128
+ end
129
+
130
+ # A String has no distance, so there is no nearest one; refused in the
131
+ # words CAString uses.
132
+ def search_nearest (*args, **kw)
133
+ to_string.search_nearest(*args, **kw)
134
+ end
135
+
136
+ # @see #search_nearest
137
+ def search_nearest_addr (*args, **kw)
138
+ to_string.search_nearest_addr(*args, **kw)
139
+ end
101
140
  end
@@ -3,22 +3,32 @@
3
3
  class CAFrame
4
4
 
5
5
  # The one rule of a frame-wide verb over the frame's columns -- the group
6
- # reductions and transforms, the windowed reductions -- kept in one place
7
- # so that every verb answers in the same shape:
6
+ # reductions and transforms, the windowed reductions, and the frame's own
7
+ # reductions and running values -- kept in one place: which columns a verb
8
+ # computes, and what a named column without the operation does.
8
9
  #
9
- # - given names, the CArray (one) or the Array of them (several), as
10
- # +df[...]+ returns columns; a named column whose type has no such
11
- # operation is refused with CArray::DataTypeError, as the core refuses
12
- # it, since it was asked for;
10
+ # - given names, those columns, as the CArray (one) or a Hash of name to
11
+ # result (several); a named column whose type has no such operation is
12
+ # refused with CArray::DataTypeError, as the core refuses it, since it
13
+ # was asked for;
13
14
  # - given none, every column whose type has it, the key columns left out,
14
- # as a Hash of name to result that the caller makes a frame of.
15
+ # as a Hash of name to result.
16
+ #
17
+ # The caller decides the shape of the answer from these: a verb whose
18
+ # answers lie on the frame's rows (a transform, a window at the rows,
19
+ # +keep_rows:+) hands one named column back as it is, as +df[name]+
20
+ # returns it, and several as a frame on the frame's rows, while a verb
21
+ # with one row per group, per point or for the whole frame makes a frame
22
+ # of them, labelled by its rows.
15
23
  #
16
24
  # Whether a type has the operation is asked of the core on two copies of
17
25
  # the column's first row (two blank cells of its kind when it has no
18
26
  # rows), so the answer is about the type and not the data: a refusal
19
27
  # there means the operation does not apply, while an error in the verb
20
- # itself is left to raise. An N-D column (each row a profile) is asked,
21
- # and computed, along the rows.
28
+ # itself is left to raise. An object column is the exception: its type
29
+ # says nothing of what it holds, so one with no present value is left out
30
+ # of a verb run over every column. An N-D column (each row a profile) is
31
+ # asked, and computed, along the rows.
22
32
  module ColumnVerbs
23
33
 
24
34
  private
@@ -29,21 +39,36 @@ class CAFrame
29
39
  @frame.column_names.each do |name|
30
40
  next if key_names.include?(name)
31
41
  col = @frame[name]
42
+ next if valueless_object_column?(col, verb, args)
32
43
  next unless column_has?(col, verb, args, keywords)
33
- cols[name] = yield(col)
44
+ cols[name] = yield(col, name)
34
45
  end
35
46
  return cols
36
47
  end
37
- results = names.map do |name|
38
- col = @frame[@frame.__send__(:column_key, name, verb)]
48
+ # A column named twice would be one result column twice over.
49
+ keys = CAFrame.__send__(:refuse_repeated_names,
50
+ names.map { |name| @frame.__send__(:column_key, name, verb) }, verb)
51
+ results = keys.map do |key|
52
+ name = key
53
+ col = @frame[key]
39
54
  unless column_has?(col, verb, args, keywords)
40
55
  raise CArray::DataTypeError,
41
56
  "#{verb}: column #{name.inspect} " \
42
57
  "(#{CArray::Inspector.type_label(col)}) has no #{verb}"
43
58
  end
44
- yield(col)
59
+ yield(col, key)
45
60
  end
46
- results.size == 1 ? results.first : results
61
+ results.size == 1 ? results.first : keys.zip(results).to_h
62
+ end
63
+
64
+ # An object column with no present cell says nothing of what it holds,
65
+ # so a verb run over every column leaves it out, as select leaves it out
66
+ # of every kind; counting the present cells applies to any column.
67
+ def valueless_object_column? (col, verb, args)
68
+ return false if col.face? || col.data_type != CA_OBJECT
69
+ return false if %i[count_not_masked count_masked].include?(verb)
70
+ return false if verb == :count && args.empty?
71
+ col.count_not_masked == 0
47
72
  end
48
73
 
49
74
  # count with no value counts the present cells, which every type has;
@@ -53,10 +78,17 @@ class CAFrame
53
78
  verb = :count_not_masked if verb == :count && args.empty?
54
79
  # Two cells: one cell does not reach what combines two (a String's
55
80
  # median interpolates, its sum adds), and an empty column answers
56
- # with the identity without asking the type at all.
81
+ # with the identity without asking the type at all. The row is the
82
+ # first that holds a present cell, so a masked first row does not
83
+ # answer with the identity for a column whose values have none.
57
84
  tail = [nil] * (col.ndim - 1)
85
+ row = 0
86
+ if col.has_mask? && col.elements > 0
87
+ present = col.is_not_masked.flatten.where
88
+ row = present[0] / (col.elements / col.shape[0]) if present.elements > 0
89
+ end
58
90
  probe = if col.shape[0] > 0
59
- col[CA_INT64([0, 0]), *tail]
91
+ col[CA_INT64([row, row]), *tail]
60
92
  else
61
93
  col.resize(2, *col.shape[1..])
62
94
  end
@@ -20,9 +20,9 @@ class CAFrame
20
20
  # preserved: writes to the result flow back to whichever input frame owns
21
21
  # the target segment, and vice versa).
22
22
  #
23
- # Per-column +data_type+ must match across frames (CArray.meld is a view
24
- # constructor that refuses to auto-cast — silent promotion here would
25
- # hide schema drift). For an eager, auto-casting alternative use
23
+ # Per-column +data_type+ must match across frames: CArray.meld would bring
24
+ # the pieces to a common type, and in a frame that promotion would hide
25
+ # schema drift. For an eager, auto-casting alternative use
26
26
  # {CAFrame.concatenate}.
27
27
  #
28
28
  # Column matching is by name (output order follows the first frame);
@@ -54,6 +54,7 @@ class CAFrame
54
54
  frames = frames_with_rows(frames)
55
55
  cols = {}
56
56
  names.each do |name|
57
+ check_same_column_type(frames.map { |f| f[name] }, name, verb: "meld")
57
58
  cols[name] = CArray.meld(frames.map { |f| f[name] }, axis: 0)
58
59
  end
59
60
  new(cols, axis_name: first.axis_name, index: meld_index(frames))
@@ -229,6 +230,19 @@ class CAFrame
229
230
  end
230
231
  private_class_method :check_trailing_shapes
231
232
 
233
+ # The pieces of one column have one data type (and one class, for a
234
+ # Face): a frame does not promote a column to make its pieces agree.
235
+ def self.check_same_column_type(pieces, name, verb:)
236
+ first = pieces.first
237
+ pieces.each_with_index do |c, i|
238
+ next if CArray::Inspector.type_label(c) == CArray::Inspector.type_label(first)
239
+ raise ArgumentError,
240
+ "#{verb}: column #{name.inspect} is #{CArray::Inspector.type_label(c)} in piece #{i}, " \
241
+ "#{CArray::Inspector.type_label(first)} in piece 0 (cast it first, or use concatenate)"
242
+ end
243
+ end
244
+ private_class_method :check_same_column_type
245
+
232
246
  # Two indexes hold the same rows when their values agree cell by cell; a
233
247
  # NaN label matches a NaN label, and a masked cell a masked cell.
234
248
  def self.same_index_values?(a, b)
@@ -26,6 +26,25 @@ class CAFrame
26
26
  each_row.map { |row| row.transform_values { |v| record_value(v) } }
27
27
  end
28
28
 
29
+ # The rows as an Array of Arrays, one value per column in column order --
30
+ # the table as plain Ruby data, for a library that takes rows (anything
31
+ # that calls +to_a+ on what it is given). A cell is what the column's own
32
+ # +to_a+ gives: a masked cell is +UNDEF+, a time column's cell its element,
33
+ # and an N-D column's cell an Array. The index is a row label, not a value,
34
+ # and is left out; +reset_index+ first to have it as the first column.
35
+ #
36
+ # Masked cells are not turned into anything: decide what they should be
37
+ # before handing the rows over, with +strip_mask+ or +drop_masked+.
38
+ #
39
+ # df.drop_masked.to_a
40
+ # df.strip_mask("temp" => Float::NAN).to_a
41
+ #
42
+ # @return [Array<Array>]
43
+ def to_a
44
+ return Array.new(@nrow) { [] } if @columns.empty?
45
+ return @columns.values.map(&:to_a).transpose
46
+ end
47
+
29
48
  # Hand the frame over as a CArray with the minimum work (memo §11.9): a
30
49
  # 2-D **view** of shape (nrow, ncol), one per column in column
31
50
  # order (§12-C). Nothing is materialised — the result is a +CAStack+ over
@@ -1,4 +1,4 @@
1
- # A small, self-contained CSV tokenizer for CAFrame.from_csv.
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.from_csv (header / skip / body /
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.from_csv`. It produces raw String cells
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 (from_csv reads them there as missing cells).
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.from_csv. A file often has
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.from_csv(path) do |r|
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.from_csv(path) { it.column_names "date", "temp", "rh"; it.data }
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
- # from_csv reads by its header: / data: / column_names: (see +layout+).
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, "from_csv: header(#{name.inspect}) is read twice"
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, "from_csv: column_names: a column name can not be empty"
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, "from_csv: columns: names no column"
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, "from_csv", "columns: a column name") if c.is_a?(Symbol)
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, "from_csv: columns: takes column names, or indexes from 0 " \
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, "from_csv: columns: a Hash selects one column per key, " \
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, "from_csv", "columns: a column 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, "from_csv: columns: a column name can not be empty " \
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, "from_csv", "columns: names")
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, "from_csv: columns: names no column #{c.inspect}" +
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, "from_csv", "columns: selects column")
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, "from_csv: columns: #{bad} is past the #{ncol} columns of the file"
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, "from_csv: column_names: gives #{names.size} " \
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 from_csv, as the verbs above.
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, "from_csv: header: takes the index of a line, from 0, " \
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, "from_csv")
540
+ attr_keys = attrs.nil? ? [] : CAFrame.send(:csv_attr_keys, attrs, "read_csv")
541
541
  if !header && !attr_keys.empty?
542
- raise ArgumentError, "from_csv: attrs: reads the lines below the header, " \
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, "from_csv: data: #{first} does not come after header: #{header}" +
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, "from_csv: data: takes the index of a line or a Range of " \
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, "from_csv: data: lines are indexed from 0 (got #{spec.inspect})"
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, "from_csv: data: #{spec.inspect} ends before it starts"
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.from_csv.
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, "from_csv: header(#{name.inspect}) on line #{line} " \
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.from_csv to build from. names is nil when
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 summarize; default all.
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 : column_keys(names, "describe")
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|