carray 3.0.3 → 3.0.5
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 +469 -0
- data/README.md +3 -2
- data/carray.gemspec +1 -1
- data/ext/ca_array_pool.c +25 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +14 -26
- 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 +231 -153
- data/ext/ca_compare.c +9 -20
- data/ext/ca_composite_dispatch.c +9 -9
- data/ext/ca_group_iter.c +16 -17
- data/ext/ca_kernel_iterator.c +1240 -2000
- data/ext/ca_kernel_iterator.h +502 -519
- data/ext/ca_monop_dispatch.c +82 -59
- data/ext/ca_monop_dispatch.h +1 -0
- data/ext/ca_obj_array.c +152 -45
- data/ext/ca_obj_bincmp.c +25 -21
- data/ext/ca_obj_binop.c +16 -26
- data/ext/ca_obj_bitarray.c +8 -3
- data/ext/ca_obj_bitfield.c +63 -49
- data/ext/ca_obj_block.c +18 -9
- data/ext/ca_obj_byte_swap.c +7 -7
- data/ext/ca_obj_const_string.c +188 -22
- data/ext/ca_obj_face.c +280 -58
- 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 +30 -8
- data/ext/ca_obj_fixlen_string.c +2 -3
- data/ext/ca_obj_grid.c +455 -134
- data/ext/ca_obj_meld.c +172 -34
- data/ext/ca_obj_moncmp.c +23 -8
- data/ext/ca_obj_monop.c +113 -67
- data/ext/ca_obj_object.c +210 -52
- data/ext/ca_obj_record.c +15 -7
- data/ext/ca_obj_reduce.c +17 -8
- data/ext/ca_obj_refer.c +42 -35
- data/ext/ca_obj_remap.c +30 -9
- data/ext/ca_obj_repeat.c +9 -10
- data/ext/ca_obj_roll.c +31 -10
- data/ext/ca_obj_select.c +48 -14
- data/ext/ca_obj_select_axis.c +15 -47
- data/ext/ca_obj_shift.c +41 -7
- data/ext/ca_obj_stack.c +180 -34
- data/ext/ca_obj_stride.c +190 -134
- data/ext/ca_obj_string.c +80 -4
- data/ext/ca_obj_tile.c +32 -11
- data/ext/ca_obj_time.c +22 -6
- data/ext/ca_obj_timedelta.c +22 -6
- data/ext/ca_obj_transpose.c +13 -7
- data/ext/ca_obj_triop.c +26 -32
- data/ext/ca_obj_window.c +267 -74
- data/ext/ca_op_ipower.c +4 -4
- data/ext/ca_sort_kernels.h +6 -3
- data/ext/ca_sweep_engine.c +2 -2
- data/ext/caframe_csv_reader.c +80 -1
- data/ext/carray.h +214 -75
- data/ext/carray_access.c +200 -99
- data/ext/carray_address_basis.c +343 -87
- data/ext/carray_attribute.c +113 -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 +375 -96
- data/ext/carray_cast_func.rb +90 -20
- data/ext/carray_class.c +4 -3
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +18 -9
- data/ext/carray_copy.c +51 -14
- data/ext/carray_core.c +150 -55
- data/ext/carray_count.c +56 -20
- data/ext/carray_element.c +81 -33
- data/ext/carray_factorize.c +138 -84
- data/ext/carray_generate.c +199 -52
- data/ext/carray_histogram.c +63 -18
- data/ext/carray_hold.c +12 -15
- data/ext/carray_index_classifier.c +388 -176
- data/ext/carray_internal.h +348 -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 +10897 -0
- data/ext/carray_kernels_triop.c +1864 -0
- data/ext/carray_key_windows.c +220 -0
- data/ext/carray_lazy.c +170 -111
- data/ext/carray_mask.c +337 -94
- data/ext/carray_median_percentile.c +82 -28
- data/ext/carray_memory_view.c +669 -561
- data/ext/carray_operator.c +129 -52
- data/ext/carray_order.c +604 -77
- data/ext/carray_partition.c +28 -27
- data/ext/carray_random.c +85 -31
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +87 -45
- data/ext/carray_segment.c +3 -3
- data/ext/carray_slab.c +123 -37
- data/ext/carray_sort.c +259 -51
- data/ext/carray_sort_kernel.c +5 -7
- data/ext/carray_struct.c +54 -61
- data/ext/carray_test.c +26 -5
- data/ext/carray_time_text.c +4 -2
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +309 -66
- data/ext/mk_call_cfunc.rb +19 -2
- data/ext/mkkernel.rb +403 -350
- data/ext/ruby_carray.c +10 -4
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +139 -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 +105 -17
- 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 +195 -59
- data/lib/carray/categorical_iterator.rb +132 -29
- data/lib/carray/complex.rb +5 -5
- data/lib/carray/conditional.rb +67 -18
- data/lib/carray/const_string.rb +24 -5
- data/lib/carray/construct.rb +15 -11
- data/lib/carray/data_type_extension.rb +9 -1
- data/lib/carray/distance_limit.rb +77 -0
- data/lib/carray/fixlen_string.rb +48 -3
- data/lib/carray/frame/column_verbs.rb +71 -0
- data/lib/carray/frame/concat.rb +17 -3
- 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 +252 -28
- data/lib/carray/frame/group.rb +603 -101
- data/lib/carray/frame/io.rb +117 -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 +42 -6
- 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 +217 -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 +61 -37
- 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 +323 -88
- data/lib/carray/serialize.rb +45 -14
- data/lib/carray/slab_iterator.rb +50 -0
- data/lib/carray/stack.rb +15 -12
- data/lib/carray/string.rb +113 -5
- data/lib/carray/string_operation_extension.rb +21 -6
- data/lib/carray/struct.rb +9 -21
- data/lib/carray/struct_builder.rb +24 -11
- data/lib/carray/time.rb +819 -158
- 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_transpose.rb +5 -4
- data/yard-stubs/ca_obj_window.rb +8 -3
- data/yard-stubs/carray_access.rb +6 -1
- data/yard-stubs/carray_attribute.rb +3 -6
- data/yard-stubs/carray_cast.rb +12 -0
- data/yard-stubs/carray_conversion.rb +7 -2
- data/yard-stubs/carray_copy.rb +4 -2
- data/yard-stubs/carray_count.rb +4 -4
- data/yard-stubs/carray_element.rb +17 -10
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_mask.rb +32 -3
- data/yard-stubs/carray_math.rb +41 -5
- data/yard-stubs/carray_median_percentile.rb +5 -2
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +129 -22
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +17 -10
- 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,25 @@ 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. The
|
|
70
|
+
# fields are read as the data fields are, so strip: trims them
|
|
71
|
+
# too
|
|
59
72
|
#
|
|
60
73
|
# CAFrame.from_csv("obs.csv", header: 2) # a title above
|
|
61
74
|
# CAFrame.from_csv("obs.csv", header: 0, data: 3) # units on lines 1-2
|
|
62
75
|
# CAFrame.from_csv("big.csv", data: 1..100) # the first 100 lines
|
|
63
76
|
# CAFrame.from_csv("raw.csv", column_names: %w[date temp rh])
|
|
64
77
|
# CAFrame.from_csv("wide.csv", columns: %w[date temp])
|
|
78
|
+
# CAFrame.from_csv("raw.csv", header: false, columns: { 0 => "date", 3 => "temp" })
|
|
79
|
+
# CAFrame.from_csv("obs.csv", attrs: [:units]) # units on line 1
|
|
65
80
|
#
|
|
66
81
|
# A block, given the reader, reads in any other order with +skip+ /
|
|
67
82
|
# +header+ / +header(name)+ / +column_names+ / +columns+ / +data+ (see
|
|
@@ -97,14 +112,19 @@ class CAFrame
|
|
|
97
112
|
# independent frame.
|
|
98
113
|
def self.from_csv(source, types: nil, on_error: :mask, missing: nil,
|
|
99
114
|
header: nil, data: nil, column_names: nil, columns: nil,
|
|
100
|
-
sep: ",", quote: '"', strip: false,
|
|
115
|
+
attrs: nil, sep: ",", quote: '"', strip: false,
|
|
101
116
|
encoding: "bom|utf-8", parser: nil, &block)
|
|
102
117
|
layout = { header: header, data: data, column_names: column_names,
|
|
103
|
-
columns: columns }.compact
|
|
118
|
+
columns: columns, attrs: attrs }.compact
|
|
104
119
|
if block && !layout.empty?
|
|
105
120
|
raise ArgumentError, "from_csv: give #{layout.keys.map { |k| "#{k}:" }.join(', ')} " \
|
|
106
121
|
"or a reading block, not both"
|
|
107
122
|
end
|
|
123
|
+
# The parser reads the header, the data and the columns itself.
|
|
124
|
+
if parser && !layout.empty?
|
|
125
|
+
raise ArgumentError, "from_csv: give #{layout.keys.map { |k| "#{k}:" }.join(', ')} " \
|
|
126
|
+
"or parser:, not both"
|
|
127
|
+
end
|
|
108
128
|
if block && block.arity != 1
|
|
109
129
|
raise ArgumentError, "from_csv: the reading block takes the reader as its " \
|
|
110
130
|
"parameter, as in from_csv(path) { |r| r.skip 2; r.header; r.data }"
|
|
@@ -116,7 +136,7 @@ class CAFrame
|
|
|
116
136
|
# body goes straight to Strings. missing: masks text in place, which
|
|
117
137
|
# wants the Strings first, so it takes that path too.
|
|
118
138
|
read_text_as_const_string = !types.nil? && missing.nil?
|
|
119
|
-
names, rows =
|
|
139
|
+
names, rows, column_attrs =
|
|
120
140
|
if parser
|
|
121
141
|
parser.call(source)
|
|
122
142
|
elsif source.respond_to?(:gets)
|
|
@@ -140,6 +160,9 @@ class CAFrame
|
|
|
140
160
|
mask_missing_tokens(frame, missing) if missing
|
|
141
161
|
frame.cast(types, on_error: on_error, option_name: :types) unless types.nil?
|
|
142
162
|
convert_const_string_columns_to_castring(frame)
|
|
163
|
+
column_attrs&.each_with_index do |attrs, j|
|
|
164
|
+
frame[frame.column_names[j]].set_attrs(attrs) unless attrs.empty?
|
|
165
|
+
end
|
|
143
166
|
frame
|
|
144
167
|
end
|
|
145
168
|
|
|
@@ -166,11 +189,9 @@ class CAFrame
|
|
|
166
189
|
missing.each do |key, value|
|
|
167
190
|
case key
|
|
168
191
|
when :default then default = value
|
|
169
|
-
when String then per_column[key] = value
|
|
170
192
|
else
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"as keys (got #{key.inspect})"
|
|
193
|
+
per_column[CAFrame.__send__(:column_key, key, verb, "missing: a column name",
|
|
194
|
+
hint: ":default is the only Symbol key")] = value
|
|
174
195
|
end
|
|
175
196
|
end
|
|
176
197
|
[default, per_column]
|
|
@@ -205,18 +226,40 @@ class CAFrame
|
|
|
205
226
|
end
|
|
206
227
|
end
|
|
207
228
|
|
|
208
|
-
|
|
229
|
+
# The attribute keys an +attrs:+ argument names, as Strings: an Array of
|
|
230
|
+
# Symbols or Strings, none twice.
|
|
231
|
+
def self.csv_attr_keys(attrs, verb)
|
|
232
|
+
unless attrs.is_a?(Array)
|
|
233
|
+
raise TypeError, "#{verb}: attrs: takes an Array of attribute keys " \
|
|
234
|
+
"(got #{attrs.inspect})"
|
|
235
|
+
end
|
|
236
|
+
keys = attrs.map do |key|
|
|
237
|
+
unless key.is_a?(Symbol) || key.is_a?(String)
|
|
238
|
+
raise TypeError, "#{verb}: attrs: an attribute key is a Symbol or String " \
|
|
239
|
+
"(got #{key.inspect})"
|
|
240
|
+
end
|
|
241
|
+
key.to_s
|
|
242
|
+
end
|
|
243
|
+
# names-check: attribute keys, not column names
|
|
244
|
+
dup = keys.tally.find { |_, n| n > 1 }
|
|
245
|
+
raise ArgumentError, "#{verb}: attrs: names #{dup.first.inspect} twice" if dup
|
|
246
|
+
keys
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
private_class_method :split_missing, :missing_tokens, :mask_missing_tokens,
|
|
250
|
+
:csv_attr_keys
|
|
209
251
|
|
|
210
252
|
# 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.
|
|
253
|
+
# [names, rows, column_attrs]. Shared by the path and the IO source, so the
|
|
254
|
+
# two cannot come to read a file differently.
|
|
213
255
|
def self.read_csv (io, sep:, quote:, strip:, layout:, read_text_as_const_string: false, &block)
|
|
214
256
|
reader = CSVReader.new(io, sep: sep, quote: quote, strip: strip,
|
|
215
257
|
read_text_as_const_string: read_text_as_const_string)
|
|
216
258
|
reader.reporting do
|
|
217
259
|
block ? block.call(reader) : reader.layout(**layout)
|
|
218
260
|
end
|
|
219
|
-
reader.result
|
|
261
|
+
names, rows = reader.result
|
|
262
|
+
[names, rows, reader.column_attrs(reader.file_ncol)]
|
|
220
263
|
end
|
|
221
264
|
|
|
222
265
|
private_class_method :read_csv
|
|
@@ -281,14 +324,27 @@ class CAFrame
|
|
|
281
324
|
#
|
|
282
325
|
# df.to_csv("out.csv", missing: "-999")
|
|
283
326
|
# df.to_csv("out.csv", missing: { default: "-999", "comment" => "" })
|
|
327
|
+
#
|
|
328
|
+
# +attrs:+ writes, below the name row, one line per attribute key given,
|
|
329
|
+
# in that order: field j is +attr(key)+ of column j (the index's for the
|
|
330
|
+
# index column). A column without the attribute gets an empty field, an
|
|
331
|
+
# empty String a quoted "", and a number its +to_s+.
|
|
332
|
+
# +from_csv(attrs:)+ with the same keys reads them back, as Strings.
|
|
333
|
+
#
|
|
334
|
+
# df.to_csv("out.csv", attrs: [:units, :long_name])
|
|
284
335
|
def to_csv(path = nil, sep: ",", quote: '"', header: true, index: true,
|
|
285
|
-
encoding: nil, missing: nil)
|
|
336
|
+
encoding: nil, missing: nil, attrs: [])
|
|
286
337
|
default, per_column = CAFrame.send(:split_missing, missing, "to_csv")
|
|
287
338
|
[default, *per_column.values].each do |text|
|
|
288
339
|
next if text.nil? || text.is_a?(String)
|
|
289
340
|
raise ArgumentError, "to_csv: missing: is the String written for a masked " \
|
|
290
341
|
"cell (got #{text.inspect})"
|
|
291
342
|
end
|
|
343
|
+
attr_keys = attrs.nil? ? [] : CAFrame.send(:csv_attr_keys, attrs, "to_csv")
|
|
344
|
+
if !header && !attr_keys.empty?
|
|
345
|
+
raise ArgumentError, "to_csv: attrs: writes lines below the name row, " \
|
|
346
|
+
"so it needs header: true"
|
|
347
|
+
end
|
|
292
348
|
nd = @columns.find { |_, c| c.ndim != 1 }
|
|
293
349
|
if nd
|
|
294
350
|
raise ArgumentError,
|
|
@@ -308,13 +364,19 @@ class CAFrame
|
|
|
308
364
|
|
|
309
365
|
names = []
|
|
310
366
|
formatted = []
|
|
367
|
+
written = []
|
|
311
368
|
if index && @index
|
|
312
369
|
names << @axis_name
|
|
313
370
|
formatted << format_csv_column(@index)
|
|
371
|
+
written << @index
|
|
314
372
|
end
|
|
315
373
|
@columns.each do |name, col|
|
|
316
374
|
names << name
|
|
317
375
|
formatted << format_csv_column(col)
|
|
376
|
+
written << col
|
|
377
|
+
end
|
|
378
|
+
attr_rows = attr_keys.map do |key|
|
|
379
|
+
written.each_with_index.map { |col, j| csv_attr_text(col, key, names[j]) }
|
|
318
380
|
end
|
|
319
381
|
unknown = per_column.keys - names
|
|
320
382
|
raise KeyError, "to_csv: missing: names no column #{unknown.first.inspect}" unless unknown.empty?
|
|
@@ -331,7 +393,7 @@ class CAFrame
|
|
|
331
393
|
end
|
|
332
394
|
|
|
333
395
|
out = begin
|
|
334
|
-
csv_text(names, formatted, header, sep, quote)
|
|
396
|
+
csv_text(names, attr_rows, formatted, header, sep, quote)
|
|
335
397
|
rescue Encoding::CompatibilityError
|
|
336
398
|
nil
|
|
337
399
|
end
|
|
@@ -340,17 +402,22 @@ class CAFrame
|
|
|
340
402
|
# UTF-8 text pays nothing for the check.
|
|
341
403
|
unless out && out.encoding == Encoding::UTF_8 && out.valid_encoding?
|
|
342
404
|
names = names.each_with_index.map { |t, j| csv_text_in_utf8(t, "the name of column #{j}") }
|
|
405
|
+
attr_rows = attr_rows.each_with_index.map do |row, k|
|
|
406
|
+
row.each_with_index.map do |t, j|
|
|
407
|
+
csv_text_in_utf8(t, "attribute #{attr_keys[k].inspect} of #{names[j].inspect}")
|
|
408
|
+
end
|
|
409
|
+
end
|
|
343
410
|
formatted = formatted.each_with_index.map do |fcol, j|
|
|
344
411
|
fcol.each_with_index.map { |t, i| csv_text_in_utf8(t, "row #{i} of #{names[j].inspect}") }
|
|
345
412
|
end
|
|
346
|
-
out = csv_text(names, formatted, header, sep, quote)
|
|
413
|
+
out = csv_text(names, attr_rows, formatted, header, sep, quote)
|
|
347
414
|
end
|
|
348
415
|
|
|
349
416
|
if encoding
|
|
350
417
|
out = begin
|
|
351
418
|
out.encode(encoding)
|
|
352
419
|
rescue Encoding::UndefinedConversionError => e
|
|
353
|
-
raise csv_unwritable_cell(names, formatted, encoding) || e
|
|
420
|
+
raise csv_unwritable_cell(names, attr_keys, attr_rows, formatted, encoding) || e
|
|
354
421
|
end
|
|
355
422
|
end
|
|
356
423
|
|
|
@@ -403,7 +470,10 @@ class CAFrame
|
|
|
403
470
|
# middle-elided table.
|
|
404
471
|
# @return [String]
|
|
405
472
|
def inspect
|
|
406
|
-
parts = @columns.map
|
|
473
|
+
parts = @columns.map do |k, v|
|
|
474
|
+
type = CArray::Inspector.type_label(v)
|
|
475
|
+
"#{k}:#{type}#{v.ndim > 1 ? v.shape[1..].inspect : ''}"
|
|
476
|
+
end
|
|
407
477
|
idx = @index ? " index=#{@axis_name.inspect}" : ""
|
|
408
478
|
head = "#<CAFrame nrow=#{@nrow} columns=[#{parts.join(', ')}]#{idx}>"
|
|
409
479
|
# The table counts the index as a column, so a frame whose only data is its
|
|
@@ -479,9 +549,11 @@ class CAFrame
|
|
|
479
549
|
0xF900..0xFAFF, 0xFE10..0xFE19, 0xFE30..0xFE6F, 0xFF00..0xFF60,
|
|
480
550
|
0xFFE0..0xFFE6, 0x1F300..0x1F64F, 0x1F900..0x1F9FF, 0x20000..0x3FFFD,
|
|
481
551
|
].freeze
|
|
552
|
+
Ractor.make_shareable(WIDE_CHAR_RANGES)
|
|
482
553
|
private_constant :WIDE_CHAR_RANGES
|
|
483
554
|
|
|
484
555
|
COMBINING_RANGES = [0x0300..0x036F, 0x1AB0..0x1AFF, 0x20D0..0x20F0].freeze
|
|
556
|
+
Ractor.make_shareable(COMBINING_RANGES)
|
|
485
557
|
private_constant :COMBINING_RANGES
|
|
486
558
|
|
|
487
559
|
private def display_width(text)
|
|
@@ -547,7 +619,7 @@ class CAFrame
|
|
|
547
619
|
# from_csv reads back as the same value, not as the double it widens
|
|
548
620
|
# to ("0.10000000149011612"); cmplx64 has that in each part.
|
|
549
621
|
if !col.face? && (col.data_type == CA_FLOAT32 || col.data_type == CA_CMPLX64)
|
|
550
|
-
col = col.
|
|
622
|
+
col = col.__widen_keeping_shortest_decimal__
|
|
551
623
|
end
|
|
552
624
|
# A boolean goes out as 0 / 1, as it does in every other serialization;
|
|
553
625
|
# from_csv reads it back with types: { name => :boolean }.
|
|
@@ -565,17 +637,33 @@ class CAFrame
|
|
|
565
637
|
end
|
|
566
638
|
end
|
|
567
639
|
|
|
568
|
-
private def csv_text(names, formatted, header, sep, quote)
|
|
640
|
+
private def csv_text(names, attr_rows, formatted, header, sep, quote)
|
|
569
641
|
out = +""
|
|
570
642
|
if header
|
|
571
643
|
out << names.map { |t| quote_csv_field(t, sep, quote) }.join(sep) << "\n"
|
|
572
644
|
end
|
|
645
|
+
attr_rows.each do |row|
|
|
646
|
+
out << row.map { |t| quote_csv_field(t, sep, quote) }.join(sep) << "\n"
|
|
647
|
+
end
|
|
573
648
|
@nrow.times do |i|
|
|
574
649
|
out << formatted.map { |fcol| quote_csv_field(fcol[i], sep, quote) }.join(sep) << "\n"
|
|
575
650
|
end
|
|
576
651
|
out
|
|
577
652
|
end
|
|
578
653
|
|
|
654
|
+
# The field +to_csv(attrs:)+ writes for attribute +key+ of +col+: nil for
|
|
655
|
+
# none (the empty field), the String itself, or a scalar's to_s.
|
|
656
|
+
private def csv_attr_text(col, key, name)
|
|
657
|
+
value = col.attr(key)
|
|
658
|
+
case value
|
|
659
|
+
when nil, String then value
|
|
660
|
+
when Array, Hash
|
|
661
|
+
raise ArgumentError, "to_csv: attribute #{key.inspect} of #{name.inspect} is " \
|
|
662
|
+
"#{value.class}, which a CSV field cannot hold"
|
|
663
|
+
else value.to_s
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
|
|
579
667
|
# The text of a cell as UTF-8, the encoding the CSV is built in: text in
|
|
580
668
|
# another encoding is transcoded; bytes that are not valid in their
|
|
581
669
|
# encoding, or have none (ASCII-8BIT), raise naming the cell.
|
|
@@ -611,8 +699,13 @@ class CAFrame
|
|
|
611
699
|
end
|
|
612
700
|
|
|
613
701
|
# The error for the first cell (or name) +encoding+ cannot hold, or nil.
|
|
614
|
-
private def csv_unwritable_cell(names, formatted, encoding)
|
|
702
|
+
private def csv_unwritable_cell(names, attr_keys, attr_rows, formatted, encoding)
|
|
615
703
|
cells = names.each_with_index.map { |t, j| [t, "the name of column #{j}"] }
|
|
704
|
+
attr_rows.each_with_index do |row, k|
|
|
705
|
+
row.each_with_index do |t, j|
|
|
706
|
+
cells << [t, "attribute #{attr_keys[k].inspect} of #{names[j].inspect}"] if t
|
|
707
|
+
end
|
|
708
|
+
end
|
|
616
709
|
formatted.each_with_index do |fcol, j|
|
|
617
710
|
fcol.each_with_index { |t, i| cells << [t, "row #{i} of #{names[j].inspect}"] if t }
|
|
618
711
|
end
|
|
@@ -654,13 +747,9 @@ class CAFrame
|
|
|
654
747
|
names ||= Array.new(ncol) { |j| "c#{j}" }
|
|
655
748
|
# A frame holds one column per name, so a repeated one would keep only
|
|
656
749
|
# 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
|
|
750
|
+
CAFrame.__send__(:refuse_repeated_names, names, "from_csv", "the header row names",
|
|
751
|
+
hint: "pass header: false for the default column names " \
|
|
752
|
+
"(c0, c1, ...), or name the columns with column_names:")
|
|
664
753
|
|
|
665
754
|
cols = {}
|
|
666
755
|
# Columns the C reader built as CAConstStrings over the text, for
|