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
data/lib/carray/frame/io.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# CAFrame CSV input/output (memo §11.2, §6-2).
|
|
2
2
|
|
|
3
3
|
require "carray/frame/csv_parser"
|
|
4
|
+
require "carray/inspect" # CArray::Inspector.face_label, for the column list
|
|
4
5
|
|
|
5
6
|
class CAFrame
|
|
6
7
|
# Read a CSV into a frame. The header row supplies column names (Strings,
|
|
@@ -14,6 +15,7 @@ class CAFrame
|
|
|
14
15
|
# CAFrame.from_csv("obs.csv", types: :infer)
|
|
15
16
|
# CAFrame.from_csv("obs.csv", types: { default: :infer, "code" => :int32, "id" => nil })
|
|
16
17
|
# CAFrame.from_csv("obs.csv", types: { default: :float64, "station" => nil })
|
|
18
|
+
# CAFrame.from_csv("obs.csv", types: { "station" => :categorical })
|
|
17
19
|
#
|
|
18
20
|
# +source+ is a path, or an open IO -- anything answering +gets+, which a
|
|
19
21
|
# StringIO is. So CSV that is already in memory does not have to go to a
|
|
@@ -40,7 +42,8 @@ class CAFrame
|
|
|
40
42
|
# Japanese.
|
|
41
43
|
# parser: a callable source -> [headers, rows] to inject another parser
|
|
42
44
|
# (e.g. the stdlib +csv+, or a typed-table source); when given,
|
|
43
|
-
# sep/quote/strip/encoding and any block are that parser's concern
|
|
45
|
+
# sep/quote/strip/encoding and any block are that parser's concern,
|
|
46
|
+
# and header:/data:/column_names:/columns:/attrs: are refused.
|
|
44
47
|
# on_error: what a +types:+ cast does with a cell that does not read
|
|
45
48
|
# (:mask default, :warn, :raise); see +cast+.
|
|
46
49
|
#
|
|
@@ -55,13 +58,23 @@ class CAFrame
|
|
|
55
58
|
# header then defaults to none
|
|
56
59
|
# columns: the columns to read, by name or by index from 0 (Integers
|
|
57
60
|
# and Ranges), in the order the frame has them; the others are
|
|
58
|
-
# read past without a String made for their fields
|
|
61
|
+
# read past without a String made for their fields. Without a
|
|
62
|
+
# header the columns are named c0, c1, ..., and those names
|
|
63
|
+
# select too. A Hash selects by its keys and names the columns
|
|
64
|
+
# with its values: { 2 => "code", "c7" => "area" }; types: and
|
|
65
|
+
# missing: then use the new names
|
|
66
|
+
# attrs: attribute keys for the lines right below the header, one
|
|
67
|
+
# key per line: field j of a line becomes that attribute of
|
|
68
|
+
# column j (an empty field gives none). The data then starts
|
|
69
|
+
# below them. What to_csv(attrs:) writes, it reads back
|
|
59
70
|
#
|
|
60
71
|
# CAFrame.from_csv("obs.csv", header: 2) # a title above
|
|
61
72
|
# CAFrame.from_csv("obs.csv", header: 0, data: 3) # units on lines 1-2
|
|
62
73
|
# CAFrame.from_csv("big.csv", data: 1..100) # the first 100 lines
|
|
63
74
|
# CAFrame.from_csv("raw.csv", column_names: %w[date temp rh])
|
|
64
75
|
# CAFrame.from_csv("wide.csv", columns: %w[date temp])
|
|
76
|
+
# CAFrame.from_csv("raw.csv", header: false, columns: { 0 => "date", 3 => "temp" })
|
|
77
|
+
# CAFrame.from_csv("obs.csv", attrs: [:units]) # units on line 1
|
|
65
78
|
#
|
|
66
79
|
# A block, given the reader, reads in any other order with +skip+ /
|
|
67
80
|
# +header+ / +header(name)+ / +column_names+ / +columns+ / +data+ (see
|
|
@@ -97,14 +110,19 @@ class CAFrame
|
|
|
97
110
|
# independent frame.
|
|
98
111
|
def self.from_csv(source, types: nil, on_error: :mask, missing: nil,
|
|
99
112
|
header: nil, data: nil, column_names: nil, columns: nil,
|
|
100
|
-
sep: ",", quote: '"', strip: false,
|
|
113
|
+
attrs: nil, sep: ",", quote: '"', strip: false,
|
|
101
114
|
encoding: "bom|utf-8", parser: nil, &block)
|
|
102
115
|
layout = { header: header, data: data, column_names: column_names,
|
|
103
|
-
columns: columns }.compact
|
|
116
|
+
columns: columns, attrs: attrs }.compact
|
|
104
117
|
if block && !layout.empty?
|
|
105
118
|
raise ArgumentError, "from_csv: give #{layout.keys.map { |k| "#{k}:" }.join(', ')} " \
|
|
106
119
|
"or a reading block, not both"
|
|
107
120
|
end
|
|
121
|
+
# The parser reads the header, the data and the columns itself.
|
|
122
|
+
if parser && !layout.empty?
|
|
123
|
+
raise ArgumentError, "from_csv: give #{layout.keys.map { |k| "#{k}:" }.join(', ')} " \
|
|
124
|
+
"or parser:, not both"
|
|
125
|
+
end
|
|
108
126
|
if block && block.arity != 1
|
|
109
127
|
raise ArgumentError, "from_csv: the reading block takes the reader as its " \
|
|
110
128
|
"parameter, as in from_csv(path) { |r| r.skip 2; r.header; r.data }"
|
|
@@ -116,7 +134,7 @@ class CAFrame
|
|
|
116
134
|
# body goes straight to Strings. missing: masks text in place, which
|
|
117
135
|
# wants the Strings first, so it takes that path too.
|
|
118
136
|
read_text_as_const_string = !types.nil? && missing.nil?
|
|
119
|
-
names, rows =
|
|
137
|
+
names, rows, column_attrs =
|
|
120
138
|
if parser
|
|
121
139
|
parser.call(source)
|
|
122
140
|
elsif source.respond_to?(:gets)
|
|
@@ -140,6 +158,9 @@ class CAFrame
|
|
|
140
158
|
mask_missing_tokens(frame, missing) if missing
|
|
141
159
|
frame.cast(types, on_error: on_error, option_name: :types) unless types.nil?
|
|
142
160
|
convert_const_string_columns_to_castring(frame)
|
|
161
|
+
column_attrs&.each_with_index do |attrs, j|
|
|
162
|
+
frame[frame.column_names[j]].set_attrs(attrs) unless attrs.empty?
|
|
163
|
+
end
|
|
143
164
|
frame
|
|
144
165
|
end
|
|
145
166
|
|
|
@@ -166,11 +187,9 @@ class CAFrame
|
|
|
166
187
|
missing.each do |key, value|
|
|
167
188
|
case key
|
|
168
189
|
when :default then default = value
|
|
169
|
-
when String then per_column[key] = value
|
|
170
190
|
else
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"as keys (got #{key.inspect})"
|
|
191
|
+
per_column[CAFrame.__send__(:column_key, key, verb, "missing: a column name",
|
|
192
|
+
hint: ":default is the only Symbol key")] = value
|
|
174
193
|
end
|
|
175
194
|
end
|
|
176
195
|
[default, per_column]
|
|
@@ -205,18 +224,40 @@ class CAFrame
|
|
|
205
224
|
end
|
|
206
225
|
end
|
|
207
226
|
|
|
208
|
-
|
|
227
|
+
# The attribute keys an +attrs:+ argument names, as Strings: an Array of
|
|
228
|
+
# Symbols or Strings, none twice.
|
|
229
|
+
def self.csv_attr_keys(attrs, verb)
|
|
230
|
+
unless attrs.is_a?(Array)
|
|
231
|
+
raise TypeError, "#{verb}: attrs: takes an Array of attribute keys " \
|
|
232
|
+
"(got #{attrs.inspect})"
|
|
233
|
+
end
|
|
234
|
+
keys = attrs.map do |key|
|
|
235
|
+
unless key.is_a?(Symbol) || key.is_a?(String)
|
|
236
|
+
raise TypeError, "#{verb}: attrs: an attribute key is a Symbol or String " \
|
|
237
|
+
"(got #{key.inspect})"
|
|
238
|
+
end
|
|
239
|
+
key.to_s
|
|
240
|
+
end
|
|
241
|
+
# names-check: attribute keys, not column names
|
|
242
|
+
dup = keys.tally.find { |_, n| n > 1 }
|
|
243
|
+
raise ArgumentError, "#{verb}: attrs: names #{dup.first.inspect} twice" if dup
|
|
244
|
+
keys
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
private_class_method :split_missing, :missing_tokens, :mask_missing_tokens,
|
|
248
|
+
:csv_attr_keys
|
|
209
249
|
|
|
210
250
|
# Drive the reading-control DSL over one open IO and hand back
|
|
211
|
-
# [names, rows]. Shared by the path and the IO source, so the
|
|
212
|
-
# come to read a file differently.
|
|
251
|
+
# [names, rows, column_attrs]. Shared by the path and the IO source, so the
|
|
252
|
+
# two cannot come to read a file differently.
|
|
213
253
|
def self.read_csv (io, sep:, quote:, strip:, layout:, read_text_as_const_string: false, &block)
|
|
214
254
|
reader = CSVReader.new(io, sep: sep, quote: quote, strip: strip,
|
|
215
255
|
read_text_as_const_string: read_text_as_const_string)
|
|
216
256
|
reader.reporting do
|
|
217
257
|
block ? block.call(reader) : reader.layout(**layout)
|
|
218
258
|
end
|
|
219
|
-
reader.result
|
|
259
|
+
names, rows = reader.result
|
|
260
|
+
[names, rows, reader.column_attrs(reader.file_ncol)]
|
|
220
261
|
end
|
|
221
262
|
|
|
222
263
|
private_class_method :read_csv
|
|
@@ -281,14 +322,27 @@ class CAFrame
|
|
|
281
322
|
#
|
|
282
323
|
# df.to_csv("out.csv", missing: "-999")
|
|
283
324
|
# df.to_csv("out.csv", missing: { default: "-999", "comment" => "" })
|
|
325
|
+
#
|
|
326
|
+
# +attrs:+ writes, below the name row, one line per attribute key given,
|
|
327
|
+
# in that order: field j is +attr(key)+ of column j (the index's for the
|
|
328
|
+
# index column). A column without the attribute gets an empty field, an
|
|
329
|
+
# empty String a quoted "", and a number its +to_s+.
|
|
330
|
+
# +from_csv(attrs:)+ with the same keys reads them back, as Strings.
|
|
331
|
+
#
|
|
332
|
+
# df.to_csv("out.csv", attrs: [:units, :long_name])
|
|
284
333
|
def to_csv(path = nil, sep: ",", quote: '"', header: true, index: true,
|
|
285
|
-
encoding: nil, missing: nil)
|
|
334
|
+
encoding: nil, missing: nil, attrs: [])
|
|
286
335
|
default, per_column = CAFrame.send(:split_missing, missing, "to_csv")
|
|
287
336
|
[default, *per_column.values].each do |text|
|
|
288
337
|
next if text.nil? || text.is_a?(String)
|
|
289
338
|
raise ArgumentError, "to_csv: missing: is the String written for a masked " \
|
|
290
339
|
"cell (got #{text.inspect})"
|
|
291
340
|
end
|
|
341
|
+
attr_keys = attrs.nil? ? [] : CAFrame.send(:csv_attr_keys, attrs, "to_csv")
|
|
342
|
+
if !header && !attr_keys.empty?
|
|
343
|
+
raise ArgumentError, "to_csv: attrs: writes lines below the name row, " \
|
|
344
|
+
"so it needs header: true"
|
|
345
|
+
end
|
|
292
346
|
nd = @columns.find { |_, c| c.ndim != 1 }
|
|
293
347
|
if nd
|
|
294
348
|
raise ArgumentError,
|
|
@@ -308,13 +362,19 @@ class CAFrame
|
|
|
308
362
|
|
|
309
363
|
names = []
|
|
310
364
|
formatted = []
|
|
365
|
+
written = []
|
|
311
366
|
if index && @index
|
|
312
367
|
names << @axis_name
|
|
313
368
|
formatted << format_csv_column(@index)
|
|
369
|
+
written << @index
|
|
314
370
|
end
|
|
315
371
|
@columns.each do |name, col|
|
|
316
372
|
names << name
|
|
317
373
|
formatted << format_csv_column(col)
|
|
374
|
+
written << col
|
|
375
|
+
end
|
|
376
|
+
attr_rows = attr_keys.map do |key|
|
|
377
|
+
written.each_with_index.map { |col, j| csv_attr_text(col, key, names[j]) }
|
|
318
378
|
end
|
|
319
379
|
unknown = per_column.keys - names
|
|
320
380
|
raise KeyError, "to_csv: missing: names no column #{unknown.first.inspect}" unless unknown.empty?
|
|
@@ -331,7 +391,7 @@ class CAFrame
|
|
|
331
391
|
end
|
|
332
392
|
|
|
333
393
|
out = begin
|
|
334
|
-
csv_text(names, formatted, header, sep, quote)
|
|
394
|
+
csv_text(names, attr_rows, formatted, header, sep, quote)
|
|
335
395
|
rescue Encoding::CompatibilityError
|
|
336
396
|
nil
|
|
337
397
|
end
|
|
@@ -340,17 +400,22 @@ class CAFrame
|
|
|
340
400
|
# UTF-8 text pays nothing for the check.
|
|
341
401
|
unless out && out.encoding == Encoding::UTF_8 && out.valid_encoding?
|
|
342
402
|
names = names.each_with_index.map { |t, j| csv_text_in_utf8(t, "the name of column #{j}") }
|
|
403
|
+
attr_rows = attr_rows.each_with_index.map do |row, k|
|
|
404
|
+
row.each_with_index.map do |t, j|
|
|
405
|
+
csv_text_in_utf8(t, "attribute #{attr_keys[k].inspect} of #{names[j].inspect}")
|
|
406
|
+
end
|
|
407
|
+
end
|
|
343
408
|
formatted = formatted.each_with_index.map do |fcol, j|
|
|
344
409
|
fcol.each_with_index.map { |t, i| csv_text_in_utf8(t, "row #{i} of #{names[j].inspect}") }
|
|
345
410
|
end
|
|
346
|
-
out = csv_text(names, formatted, header, sep, quote)
|
|
411
|
+
out = csv_text(names, attr_rows, formatted, header, sep, quote)
|
|
347
412
|
end
|
|
348
413
|
|
|
349
414
|
if encoding
|
|
350
415
|
out = begin
|
|
351
416
|
out.encode(encoding)
|
|
352
417
|
rescue Encoding::UndefinedConversionError => e
|
|
353
|
-
raise csv_unwritable_cell(names, formatted, encoding) || e
|
|
418
|
+
raise csv_unwritable_cell(names, attr_keys, attr_rows, formatted, encoding) || e
|
|
354
419
|
end
|
|
355
420
|
end
|
|
356
421
|
|
|
@@ -403,7 +468,10 @@ class CAFrame
|
|
|
403
468
|
# middle-elided table.
|
|
404
469
|
# @return [String]
|
|
405
470
|
def inspect
|
|
406
|
-
parts = @columns.map
|
|
471
|
+
parts = @columns.map do |k, v|
|
|
472
|
+
type = CArray::Inspector.type_label(v)
|
|
473
|
+
"#{k}:#{type}#{v.ndim > 1 ? v.shape[1..].inspect : ''}"
|
|
474
|
+
end
|
|
407
475
|
idx = @index ? " index=#{@axis_name.inspect}" : ""
|
|
408
476
|
head = "#<CAFrame nrow=#{@nrow} columns=[#{parts.join(', ')}]#{idx}>"
|
|
409
477
|
# The table counts the index as a column, so a frame whose only data is its
|
|
@@ -479,9 +547,11 @@ class CAFrame
|
|
|
479
547
|
0xF900..0xFAFF, 0xFE10..0xFE19, 0xFE30..0xFE6F, 0xFF00..0xFF60,
|
|
480
548
|
0xFFE0..0xFFE6, 0x1F300..0x1F64F, 0x1F900..0x1F9FF, 0x20000..0x3FFFD,
|
|
481
549
|
].freeze
|
|
550
|
+
Ractor.make_shareable(WIDE_CHAR_RANGES)
|
|
482
551
|
private_constant :WIDE_CHAR_RANGES
|
|
483
552
|
|
|
484
553
|
COMBINING_RANGES = [0x0300..0x036F, 0x1AB0..0x1AFF, 0x20D0..0x20F0].freeze
|
|
554
|
+
Ractor.make_shareable(COMBINING_RANGES)
|
|
485
555
|
private_constant :COMBINING_RANGES
|
|
486
556
|
|
|
487
557
|
private def display_width(text)
|
|
@@ -547,7 +617,7 @@ class CAFrame
|
|
|
547
617
|
# from_csv reads back as the same value, not as the double it widens
|
|
548
618
|
# to ("0.10000000149011612"); cmplx64 has that in each part.
|
|
549
619
|
if !col.face? && (col.data_type == CA_FLOAT32 || col.data_type == CA_CMPLX64)
|
|
550
|
-
col = col.
|
|
620
|
+
col = col.__widen_keeping_shortest_decimal__
|
|
551
621
|
end
|
|
552
622
|
# A boolean goes out as 0 / 1, as it does in every other serialization;
|
|
553
623
|
# from_csv reads it back with types: { name => :boolean }.
|
|
@@ -565,17 +635,33 @@ class CAFrame
|
|
|
565
635
|
end
|
|
566
636
|
end
|
|
567
637
|
|
|
568
|
-
private def csv_text(names, formatted, header, sep, quote)
|
|
638
|
+
private def csv_text(names, attr_rows, formatted, header, sep, quote)
|
|
569
639
|
out = +""
|
|
570
640
|
if header
|
|
571
641
|
out << names.map { |t| quote_csv_field(t, sep, quote) }.join(sep) << "\n"
|
|
572
642
|
end
|
|
643
|
+
attr_rows.each do |row|
|
|
644
|
+
out << row.map { |t| quote_csv_field(t, sep, quote) }.join(sep) << "\n"
|
|
645
|
+
end
|
|
573
646
|
@nrow.times do |i|
|
|
574
647
|
out << formatted.map { |fcol| quote_csv_field(fcol[i], sep, quote) }.join(sep) << "\n"
|
|
575
648
|
end
|
|
576
649
|
out
|
|
577
650
|
end
|
|
578
651
|
|
|
652
|
+
# The field +to_csv(attrs:)+ writes for attribute +key+ of +col+: nil for
|
|
653
|
+
# none (the empty field), the String itself, or a scalar's to_s.
|
|
654
|
+
private def csv_attr_text(col, key, name)
|
|
655
|
+
value = col.attr(key)
|
|
656
|
+
case value
|
|
657
|
+
when nil, String then value
|
|
658
|
+
when Array, Hash
|
|
659
|
+
raise ArgumentError, "to_csv: attribute #{key.inspect} of #{name.inspect} is " \
|
|
660
|
+
"#{value.class}, which a CSV field cannot hold"
|
|
661
|
+
else value.to_s
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
579
665
|
# The text of a cell as UTF-8, the encoding the CSV is built in: text in
|
|
580
666
|
# another encoding is transcoded; bytes that are not valid in their
|
|
581
667
|
# encoding, or have none (ASCII-8BIT), raise naming the cell.
|
|
@@ -611,8 +697,13 @@ class CAFrame
|
|
|
611
697
|
end
|
|
612
698
|
|
|
613
699
|
# The error for the first cell (or name) +encoding+ cannot hold, or nil.
|
|
614
|
-
private def csv_unwritable_cell(names, formatted, encoding)
|
|
700
|
+
private def csv_unwritable_cell(names, attr_keys, attr_rows, formatted, encoding)
|
|
615
701
|
cells = names.each_with_index.map { |t, j| [t, "the name of column #{j}"] }
|
|
702
|
+
attr_rows.each_with_index do |row, k|
|
|
703
|
+
row.each_with_index do |t, j|
|
|
704
|
+
cells << [t, "attribute #{attr_keys[k].inspect} of #{names[j].inspect}"] if t
|
|
705
|
+
end
|
|
706
|
+
end
|
|
616
707
|
formatted.each_with_index do |fcol, j|
|
|
617
708
|
fcol.each_with_index { |t, i| cells << [t, "row #{i} of #{names[j].inspect}"] if t }
|
|
618
709
|
end
|
|
@@ -654,13 +745,9 @@ class CAFrame
|
|
|
654
745
|
names ||= Array.new(ncol) { |j| "c#{j}" }
|
|
655
746
|
# A frame holds one column per name, so a repeated one would keep only
|
|
656
747
|
# the last of its columns.
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
"the header names #{dup.map(&:inspect).join(', ')} more than once; " \
|
|
661
|
-
"name the columns yourself: from_csv(path, header: 0, " \
|
|
662
|
-
"column_names: [...])"
|
|
663
|
-
end
|
|
748
|
+
CAFrame.__send__(:refuse_repeated_names, names, "from_csv", "the header row names",
|
|
749
|
+
hint: "pass header: false for the default column names " \
|
|
750
|
+
"(c0, c1, ...), or name the columns with column_names:")
|
|
664
751
|
|
|
665
752
|
cols = {}
|
|
666
753
|
# Columns the C reader built as CAConstStrings over the text, for
|