carray 3.0.2 → 3.0.3
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 +304 -0
- data/README.md +3 -4
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +225 -0
- data/ext/ca_axis_group.c +177 -99
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +66 -71
- data/ext/ca_compare.c +8 -2
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +27 -11
- data/ext/ca_kernel_iterator.c +663 -214
- data/ext/ca_kernel_iterator.h +231 -167
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +132 -22
- data/ext/ca_obj_bincmp.c +152 -121
- data/ext/ca_obj_binop.c +107 -210
- data/ext/ca_obj_bitarray.c +37 -24
- data/ext/ca_obj_bitfield.c +95 -62
- data/ext/ca_obj_block.c +1 -0
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +96 -25
- data/ext/ca_obj_face.c +406 -64
- data/ext/ca_obj_face.h +92 -47
- data/ext/ca_obj_fake.c +21 -50
- data/ext/ca_obj_farray.c +1 -0
- data/ext/ca_obj_field.c +16 -3
- data/ext/ca_obj_fixlen_string.c +21 -21
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +189 -107
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +543 -233
- data/ext/ca_obj_record.c +51 -16
- data/ext/ca_obj_reduce.c +5 -8
- data/ext/ca_obj_refer.c +117 -33
- data/ext/ca_obj_remap.c +57 -17
- data/ext/ca_obj_repeat.c +21 -5
- data/ext/ca_obj_roll.c +115 -11
- data/ext/ca_obj_select.c +47 -49
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -118
- data/ext/ca_obj_stride.c +195 -34
- data/ext/ca_obj_string.c +17 -18
- data/ext/ca_obj_tile.c +32 -16
- data/ext/ca_obj_time.c +23 -25
- data/ext/ca_obj_timedelta.c +21 -23
- data/ext/ca_obj_transpose.c +12 -3
- data/ext/ca_obj_triop.c +110 -117
- data/ext/ca_obj_window.c +149 -27
- data/ext/ca_op_ipower.c +24 -11
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +102 -79
- data/ext/ca_sweep_engine.h +7 -20
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +463 -0
- data/ext/carray.h +70 -27
- data/ext/carray_access.c +455 -211
- data/ext/carray_address_basis.c +177 -36
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +46 -14
- data/ext/carray_call_cfunc.c +0 -28
- data/ext/carray_cast.c +1444 -209
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +32 -11
- data/ext/carray_core.c +514 -122
- data/ext/carray_count.c +140 -87
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +569 -277
- data/ext/carray_generate.c +76 -40
- data/ext/carray_histogram.c +152 -94
- data/ext/carray_hold.c +2 -1
- data/ext/carray_index_classifier.c +63 -96
- data/ext/carray_internal.h +385 -3
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +81 -65
- data/ext/carray_mask.c +682 -316
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +221 -76
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1089 -359
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +273 -239
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +170 -70
- data/ext/carray_sort.c +215 -218
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +99 -21
- data/ext/carray_test.c +167 -64
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_utils.c +363 -98
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +0 -2
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1076 -846
- data/ext/ruby_carray.c +7 -1
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +39 -0
- data/lib/carray/autoload_carray.rb +19 -0
- data/lib/carray/autoload_method_extension.rb +6 -0
- data/lib/carray/axis_group.rb +174 -26
- data/lib/carray/basics.rb +91 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +118 -48
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +94 -26
- data/lib/carray/categorical_iterator.rb +333 -826
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +46 -0
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +589 -82
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +124 -25
- data/lib/carray/frame/group.rb +189 -63
- data/lib/carray/frame/io.rb +412 -72
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +37 -16
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +715 -48
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +111 -2
- data/lib/carray/fusion.rb +229 -38
- data/lib/carray/inspect.rb +29 -11
- data/lib/carray/iterator.rb +14 -2
- data/lib/carray/lazy.rb +54 -30
- data/lib/carray/mask_gap_fill.rb +66 -52
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +40 -7
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bincount.rb +2 -2
- data/lib/carray/methods/bit_string.rb +14 -3
- data/lib/carray/methods/composition.rb +16 -10
- data/lib/carray/methods/factorize.rb +2 -2
- data/lib/carray/methods/gather_nd.rb +13 -2
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_in.rb +117 -4
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +177 -36
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/repeat.rb +1 -20
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +11 -2
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/runtime.rb +14 -11
- data/lib/carray/segment_iterator.rb +990 -0
- data/lib/carray/serialize.rb +197 -27
- data/lib/carray/slab_iterator.rb +51 -27
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +4 -3
- data/lib/carray/struct.rb +142 -51
- data/lib/carray/struct_builder.rb +110 -14
- data/lib/carray/table.rb +2 -2
- data/lib/carray/time.rb +517 -103
- data/lib/carray/window_iterator.rb +127 -72
- data/lib/carray.rb +2 -2
- data/yard-stubs/ca_obj_array.rb +4 -4
- data/yard-stubs/ca_obj_bitarray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +23 -12
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +1 -1
- data/yard-stubs/ca_obj_refer.rb +3 -3
- data/yard-stubs/ca_obj_roll.rb +0 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +1 -2
- data/yard-stubs/carray_access.rb +5 -4
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_cast.rb +78 -43
- data/yard-stubs/carray_conversion.rb +13 -1
- data/yard-stubs/carray_copy.rb +8 -3
- data/yard-stubs/carray_core.rb +3 -3
- data/yard-stubs/carray_count.rb +9 -0
- data/yard-stubs/carray_lazy.rb +5 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +25 -13
- data/yard-stubs/carray_median_percentile.rb +10 -6
- data/yard-stubs/carray_order.rb +34 -13
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +2 -1
- data/yard-stubs/carray_sort.rb +43 -3
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +11 -19
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_build_flags.h +0 -3
- data/ext/carray_config.h +0 -28
- data/ext/carray_kernels_bincmp.c +0 -4446
- data/ext/carray_kernels_binop.c +0 -11001
- data/ext/carray_kernels_init.c +0 -1131
- data/ext/carray_kernels_map.c +0 -3467
- data/ext/carray_kernels_moncmp.c +0 -2097
- data/ext/carray_kernels_monop.c +0 -18313
- data/ext/carray_kernels_reduce_aggregate.c +0 -26005
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14862
- data/ext/carray_kernels_reduce_extreme.c +0 -17494
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32218
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# CAFrame#describe: one row of statistics per column, for a first look at a
|
|
2
|
+
# table that has just been read.
|
|
3
|
+
|
|
4
|
+
class CAFrame
|
|
5
|
+
# Summarize the columns, one row each, as a new frame indexed by column
|
|
6
|
+
# name:
|
|
7
|
+
#
|
|
8
|
+
# puts df.describe.to_table
|
|
9
|
+
#
|
|
10
|
+
# column type count masked unique min max mean stddev
|
|
11
|
+
# ------- ------- ----- ------ ------ ---------- ---------- ---------- --------
|
|
12
|
+
# station object 3 0 2 _ _ _ _
|
|
13
|
+
# temp float64 2 1 2 19.0 22.1 20.55 2.192031
|
|
14
|
+
# time CATime 3 0 3 2024-01-01 2024-01-03 2024-01-02 1D
|
|
15
|
+
#
|
|
16
|
+
# +type+ is the data type, or the Face's class for a Face column, with the
|
|
17
|
+
# trailing shape of an N-D column in brackets (+float64[3]+). +count+ and
|
|
18
|
+
# +masked+ count cells, so an N-D column counts every cell of every row;
|
|
19
|
+
# +unique+ is the number of distinct values among the present cells.
|
|
20
|
+
#
|
|
21
|
+
# The other statistics apply only where the column's kind gives them a
|
|
22
|
+
# meaning, and are UNDEF elsewhere:
|
|
23
|
+
#
|
|
24
|
+
# real numbers, boolean min max mean stddev
|
|
25
|
+
# complex mean stddev (no order)
|
|
26
|
+
# CATime, CATimedelta min max mean stddev (as times / durations)
|
|
27
|
+
# anything else (text, categorical, records ...) unique only
|
|
28
|
+
#
|
|
29
|
+
# A boolean column counts true as 1, so its mean is the share of trues. A
|
|
30
|
+
# column with no present cell has UNDEF statistics. The index is not a
|
|
31
|
+
# column and is not summarized.
|
|
32
|
+
#
|
|
33
|
+
# @param names [Array<String>] the columns to summarize; default all.
|
|
34
|
+
# @return [CAFrame] a new frame with one row per column.
|
|
35
|
+
def describe(*names)
|
|
36
|
+
names = names.empty? ? column_names : names.map(&:to_s)
|
|
37
|
+
rows = names.map { |name| describe_column(self[name]) }
|
|
38
|
+
stats = %w[type count masked unique min max mean stddev]
|
|
39
|
+
cols = stats.to_h do |stat|
|
|
40
|
+
values = rows.map { |row| row[stat] }
|
|
41
|
+
column =
|
|
42
|
+
case stat
|
|
43
|
+
when "count", "masked", "unique" then CArray.int64(values.size)
|
|
44
|
+
else CArray.object(values.size)
|
|
45
|
+
end
|
|
46
|
+
column[] = UNDEF
|
|
47
|
+
values.each_with_index { |v, i| column[i] = v unless UNDEF.equal?(v) }
|
|
48
|
+
[stat, column]
|
|
49
|
+
end
|
|
50
|
+
CAFrame.new(cols, axis_name: "column", index: CA_OBJECT(names))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
DESCRIBE_TIME_FACES = %w[CATime CATimedelta].freeze
|
|
54
|
+
private_constant :DESCRIBE_TIME_FACES
|
|
55
|
+
|
|
56
|
+
private def describe_column(col)
|
|
57
|
+
kind =
|
|
58
|
+
if col.face?
|
|
59
|
+
DESCRIBE_TIME_FACES.include?(col.class.name) ? :ordered : :other
|
|
60
|
+
elsif col.complex?
|
|
61
|
+
:complex
|
|
62
|
+
elsif col.numeric? || col.boolean?
|
|
63
|
+
:ordered
|
|
64
|
+
else
|
|
65
|
+
:other
|
|
66
|
+
end
|
|
67
|
+
type = col.face? ? col.class.name : col.data_type_name
|
|
68
|
+
type += col.shape[1..].inspect if col.ndim > 1
|
|
69
|
+
row = { "type" => type,
|
|
70
|
+
"count" => col.count_not_masked, "masked" => col.count_masked,
|
|
71
|
+
"unique" => col.nunique }
|
|
72
|
+
ordered = kind == :ordered
|
|
73
|
+
row["min"] = ordered ? col.min : UNDEF
|
|
74
|
+
row["max"] = ordered ? col.max : UNDEF
|
|
75
|
+
row["mean"] = kind == :other ? UNDEF : col.mean
|
|
76
|
+
row["stddev"] = kind == :other ? UNDEF : col.stddev
|
|
77
|
+
row
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/carray/frame/frame.rb
CHANGED
|
@@ -11,6 +11,29 @@
|
|
|
11
11
|
# storage, and +copy+ is the way to an independent frame.
|
|
12
12
|
|
|
13
13
|
class CAFrame
|
|
14
|
+
# Raised when the contents of a column cannot be read as asked: a cell
|
|
15
|
+
# that does not read as the type a +cast+ (or +from_csv+'s +types:+) asks
|
|
16
|
+
# for under on_error: :raise, a cell +parse_to_time+ cannot read, a column
|
|
17
|
+
# whose time format +infer_time_format+ / parse_to_time(name, :infer)
|
|
18
|
+
# cannot find, or a float column +to_time+ cannot read as whole counts.
|
|
19
|
+
# The message names the column and the cell. The arguments were fine; the
|
|
20
|
+
# column's contents are what did not read.
|
|
21
|
+
class UnreadableColumn < StandardError; end
|
|
22
|
+
|
|
23
|
+
# Raised when +infer_time_format+ / parse_to_time(name, :infer) finds more
|
|
24
|
+
# than one format every cell fits ("01/02/2024" and "03/04/2024" are both
|
|
25
|
+
# day-first and month-first). The text reads in each of them, so it is
|
|
26
|
+
# not an UnreadableColumn; +formats+ gives the candidates, to choose one and
|
|
27
|
+
# pass it as the format.
|
|
28
|
+
class AmbiguousTimeFormat < StandardError
|
|
29
|
+
attr_reader :formats
|
|
30
|
+
|
|
31
|
+
def initialize(message = nil, formats: [])
|
|
32
|
+
@formats = formats
|
|
33
|
+
super(message)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
14
37
|
# Integer data_types that select rows positionally when used as a df[] key.
|
|
15
38
|
INTEGER_TYPES = [:int8, :int16, :int32, :int64,
|
|
16
39
|
:uint8, :uint16, :uint32, :uint64].freeze
|
|
@@ -112,7 +135,7 @@ class CAFrame
|
|
|
112
135
|
if keys.size > 1
|
|
113
136
|
unless keys.all? { |k| k.is_a?(String) }
|
|
114
137
|
raise ArgumentError,
|
|
115
|
-
"
|
|
138
|
+
"df[...] with several keys returns columns; every key must be a String " \
|
|
116
139
|
"(use df.select(...) for a subset frame)"
|
|
117
140
|
end
|
|
118
141
|
return keys.map { |k| @columns.fetch(k) { raise KeyError, "no column #{k.inspect}" } }
|
|
@@ -142,7 +165,7 @@ class CAFrame
|
|
|
142
165
|
"CArray df[] key must be boolean or integer (got #{key.data_type})"
|
|
143
166
|
end
|
|
144
167
|
when Symbol
|
|
145
|
-
raise
|
|
168
|
+
raise ArgumentError, "df[symbol] is reserved for predicate keys"
|
|
146
169
|
else
|
|
147
170
|
raise ArgumentError, "unsupported df[] key: #{key.class}"
|
|
148
171
|
end
|
|
@@ -197,6 +220,7 @@ class CAFrame
|
|
|
197
220
|
# write path always propagates to derived views (unchanged from
|
|
198
221
|
# previous behaviour).
|
|
199
222
|
def []=(*keys, value)
|
|
223
|
+
refuse_if_frozen
|
|
200
224
|
if keys.size != 1
|
|
201
225
|
raise ArgumentError,
|
|
202
226
|
"df[...] = takes one key (a column name or a row selector); " \
|
|
@@ -222,6 +246,52 @@ class CAFrame
|
|
|
222
246
|
value
|
|
223
247
|
end
|
|
224
248
|
|
|
249
|
+
# Freezing a frame fixes its columns, rows and index; the columns' cells are
|
|
250
|
+
# their own arrays' to guard, as an Array's elements are. The check comes
|
|
251
|
+
# first so a refused edit leaves the frame as it was: the edits change the
|
|
252
|
+
# column table before any instance variable.
|
|
253
|
+
private def refuse_if_frozen
|
|
254
|
+
raise FrozenError.new("can't modify frozen CAFrame: #{inspect}", receiver: self) if frozen?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Make this frame read-only and return it. It is +freeze+ extended to the
|
|
258
|
+
# cells: the column set, the rows and the index are fixed as by +freeze+,
|
|
259
|
+
# and the cells and masks of every column and of the index refuse writes
|
|
260
|
+
# made through this frame.
|
|
261
|
+
#
|
|
262
|
+
# Each column (and the index) is replaced by a read-only view of itself, so
|
|
263
|
+
# the arrays the frame was built from are left as they were: whoever holds
|
|
264
|
+
# them can still write, and the writes show through this frame. Frames
|
|
265
|
+
# derived from a protected frame by views (+select+, +filter+, a row slice)
|
|
266
|
+
# read through those views and refuse cell writes too, but are not frozen.
|
|
267
|
+
# +copy+ gives a frame that can be written again.
|
|
268
|
+
#
|
|
269
|
+
# Calling it on a protected frame returns the frame unchanged. A frame
|
|
270
|
+
# frozen with +freeze+ cannot be protected afterwards, because its columns
|
|
271
|
+
# can no longer be replaced: raises +FrozenError+.
|
|
272
|
+
def protect
|
|
273
|
+
return self if protected?
|
|
274
|
+
refuse_if_frozen
|
|
275
|
+
@columns = @columns.transform_values { |col| read_only_view(col) }
|
|
276
|
+
@index = read_only_view(@index) if @index
|
|
277
|
+
@protected = true
|
|
278
|
+
freeze
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# True if this frame was made read-only by +protect+. A +dup+ is not frozen
|
|
282
|
+
# and so not protected, though its columns stay the read-only views it
|
|
283
|
+
# shares; a +clone+ keeps both.
|
|
284
|
+
def protected?
|
|
285
|
+
frozen? && @protected == true
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
private def read_only_view(col)
|
|
289
|
+
return col if col.read_only?
|
|
290
|
+
view = col.refer
|
|
291
|
+
view.set_read_only_flag
|
|
292
|
+
view
|
|
293
|
+
end
|
|
294
|
+
|
|
225
295
|
# df["name"] = ... : the column forms of []=. Unlike the verbs, +[]=+ can
|
|
226
296
|
# only ever mutate the receiver -- Ruby hands the right-hand side back as
|
|
227
297
|
# the value of an assignment, so there is no way to return a new frame.
|
|
@@ -238,7 +308,7 @@ class CAFrame
|
|
|
238
308
|
mask_column(key)
|
|
239
309
|
elsif value.is_a?(CAFrame)
|
|
240
310
|
raise ArgumentError,
|
|
241
|
-
"df[name] = takes a column, not a CAFrame;
|
|
311
|
+
"df[name] = takes a column, not a CAFrame; take one with " \
|
|
242
312
|
"other[\"name\"], or splice rows with df[rows] = other"
|
|
243
313
|
else
|
|
244
314
|
rebind_column(key, value)
|
|
@@ -331,9 +401,9 @@ class CAFrame
|
|
|
331
401
|
raise ArgumentError,
|
|
332
402
|
"filter block must return a boolean CArray (got #{mask.class})"
|
|
333
403
|
end
|
|
334
|
-
unless mask.shape
|
|
404
|
+
unless mask.shape == [@nrow]
|
|
335
405
|
raise ArgumentError,
|
|
336
|
-
"filter mask has
|
|
406
|
+
"filter mask has shape #{mask.shape.inspect}, expected [#{@nrow}] (one value per row)"
|
|
337
407
|
end
|
|
338
408
|
select_rows(mask, keep_masked: keep_masked)
|
|
339
409
|
end
|
|
@@ -342,6 +412,7 @@ class CAFrame
|
|
|
342
412
|
# An index-role change: the data is unchanged, so this mutates self and
|
|
343
413
|
# returns it (memo §3.8). Any existing index is replaced.
|
|
344
414
|
def set_index(name)
|
|
415
|
+
refuse_if_frozen
|
|
345
416
|
key = name.to_s
|
|
346
417
|
raise KeyError, "no column #{key.inspect}" unless @columns.key?(key)
|
|
347
418
|
idx = @columns[key]
|
|
@@ -370,6 +441,7 @@ class CAFrame
|
|
|
370
441
|
# Also an index-role change, so it mutates self (memo §3.8). The former
|
|
371
442
|
# index becomes the first column, named after the row axis.
|
|
372
443
|
def reset_index
|
|
444
|
+
refuse_if_frozen
|
|
373
445
|
return self unless @index
|
|
374
446
|
@columns = { @axis_name => @index }.merge(@columns)
|
|
375
447
|
@index = nil
|
|
@@ -394,7 +466,11 @@ class CAFrame
|
|
|
394
466
|
|
|
395
467
|
# The single row whose index label equals +label+, as a Ruby Hash (memo
|
|
396
468
|
# §13.2b). +label+ is matched exactly against the index (+index.eq+), so any
|
|
397
|
-
# orderable / object / datetime / categorical index works
|
|
469
|
+
# orderable / object / datetime / categorical index works, and +label+ is
|
|
470
|
+
# whatever +index.eq+ takes -- for a time index, a Time, Date or a String
|
|
471
|
+
# such as "2024-01-02". A NaN label finds the NaN label, as join and
|
|
472
|
+
# group_by match NaN keys. A numeric index takes a number; a String or
|
|
473
|
+
# true / false raises. The return type is
|
|
398
474
|
# a row Hash and stays that way: zero matches raise KeyError, and duplicate
|
|
399
475
|
# labels raise (go through +filter { |f| f.index.eq(label) }+ for the
|
|
400
476
|
# multi-row, frame-returning path). Positional access is +df[i]+.
|
|
@@ -411,16 +487,29 @@ class CAFrame
|
|
|
411
487
|
"at(UNDEF): an undefined label identifies no row; " \
|
|
412
488
|
"use filter { |f| f.index.is_masked } for the rows with no label"
|
|
413
489
|
end
|
|
414
|
-
|
|
490
|
+
# A NaN label is the NaN value, as join and group_by take it: eq answers
|
|
491
|
+
# false for NaN against anything, itself included.
|
|
492
|
+
# A numeric index is looked up with a number: a String or true/false would
|
|
493
|
+
# be read differently by each numeric type, or not at all.
|
|
494
|
+
if !@index.face? && (@index.integer? || @index.float? || @index.complex?) &&
|
|
495
|
+
!label.is_a?(Numeric)
|
|
496
|
+
raise ArgumentError,
|
|
497
|
+
"at: the index is #{@index.data_type}, so the label must be a number " \
|
|
498
|
+
"(got #{label.inspect}); convert it first, e.g. at(Float(s)) or at(Integer(s))"
|
|
499
|
+
end
|
|
500
|
+
nan = label.is_a?(Float) && label.nan?
|
|
501
|
+
hit = nan ? @index.is_in([label]) : @index.eq(label)
|
|
502
|
+
pos = hit.where
|
|
415
503
|
case pos.elements
|
|
416
504
|
when 0
|
|
417
505
|
raise KeyError, "no row with index label #{label.inspect}"
|
|
418
506
|
when 1
|
|
419
507
|
row(pos[0])
|
|
420
508
|
else
|
|
509
|
+
test = nan ? "f.index.is_nan" : "f.index.eq(label)"
|
|
421
510
|
raise ArgumentError,
|
|
422
511
|
"index label #{label.inspect} matches #{pos.elements} rows; " \
|
|
423
|
-
"use filter { |f|
|
|
512
|
+
"use filter { |f| #{test} } for duplicate labels"
|
|
424
513
|
end
|
|
425
514
|
end
|
|
426
515
|
|
|
@@ -444,20 +533,20 @@ class CAFrame
|
|
|
444
533
|
# --- metadata readers (memo §13.2) ------------------------------------
|
|
445
534
|
# Each returns a fresh object; the live columns Hash is never exposed.
|
|
446
535
|
|
|
447
|
-
#
|
|
448
|
-
def
|
|
536
|
+
# Column names in column order. Returns +Array<String>+.
|
|
537
|
+
def column_names
|
|
449
538
|
@columns.keys
|
|
450
539
|
end
|
|
451
540
|
|
|
452
|
-
#
|
|
453
|
-
#
|
|
454
|
-
#
|
|
455
|
-
def
|
|
541
|
+
# The columns (raw CArrays) in column order. Returns +Array<CArray>+.
|
|
542
|
+
# Equivalent to +df[*column_names]+ but built directly from the columns
|
|
543
|
+
# Hash.
|
|
544
|
+
def columns
|
|
456
545
|
@columns.values
|
|
457
546
|
end
|
|
458
547
|
|
|
459
|
-
# Number of
|
|
460
|
-
def
|
|
548
|
+
# Number of columns.
|
|
549
|
+
def ncol
|
|
461
550
|
@columns.size
|
|
462
551
|
end
|
|
463
552
|
|
|
@@ -485,7 +574,7 @@ class CAFrame
|
|
|
485
574
|
if col.is_a?(CAFrame)
|
|
486
575
|
raise ArgumentError,
|
|
487
576
|
"a CAFrame cannot be a column (it answers to_ca as a 2-D matrix); " \
|
|
488
|
-
"
|
|
577
|
+
"take a column with df[\"name\"] or df.to_ca first"
|
|
489
578
|
end
|
|
490
579
|
unless col.respond_to?(:to_ca)
|
|
491
580
|
raise ArgumentError, "column must be a CArray or Array (got #{col.class})"
|
|
@@ -498,10 +587,11 @@ class CAFrame
|
|
|
498
587
|
end
|
|
499
588
|
|
|
500
589
|
private def row(i)
|
|
501
|
-
i
|
|
502
|
-
unless
|
|
590
|
+
pos = i < 0 ? i + @nrow : i
|
|
591
|
+
unless pos >= 0 && pos < @nrow
|
|
503
592
|
raise IndexError, "row index #{i} out of range (nrow=#{@nrow})"
|
|
504
593
|
end
|
|
594
|
+
i = pos
|
|
505
595
|
h = {}
|
|
506
596
|
h[@axis_name] = elem_at(@index, i) if @index
|
|
507
597
|
@columns.each { |name, col| h[name] = elem_at(col, i) }
|
|
@@ -562,10 +652,10 @@ class CAFrame
|
|
|
562
652
|
# promote and the row count shifts by other.nrow - span.
|
|
563
653
|
private def splice_rows(selector, other)
|
|
564
654
|
lo, hi = contiguous_span(selector)
|
|
565
|
-
unless other.
|
|
655
|
+
unless other.column_names.sort == column_names.sort
|
|
566
656
|
raise ArgumentError,
|
|
567
|
-
"splice frame has columns #{other.
|
|
568
|
-
"expected the same set as #{
|
|
657
|
+
"splice frame has columns #{other.column_names.inspect}, " \
|
|
658
|
+
"expected the same set as #{column_names.inspect}"
|
|
569
659
|
end
|
|
570
660
|
new_cols = {}
|
|
571
661
|
@columns.each do |name, col|
|
|
@@ -691,11 +781,20 @@ class CAFrame
|
|
|
691
781
|
keep = selector.strip_mask(1) # true where selected OR undetermined
|
|
692
782
|
undet_kept = selector.is_masked[keep] # among kept rows, which were undetermined
|
|
693
783
|
cols = {}
|
|
784
|
+
any_undet = undet_kept.any
|
|
694
785
|
@columns.each do |name, col|
|
|
695
786
|
tail = [nil] * (col.ndim - 1)
|
|
696
|
-
g = col[keep, *tail]
|
|
697
|
-
|
|
698
|
-
|
|
787
|
+
g = col[keep, *tail]
|
|
788
|
+
if any_undet
|
|
789
|
+
# mask_where copies and masks in one step, so a read-only Face
|
|
790
|
+
# (categorical, CAConstString) takes the mask as any column does.
|
|
791
|
+
cells = CArray.boolean(*g.shape)
|
|
792
|
+
cells[] = false
|
|
793
|
+
cells[undet_kept, *tail] = true
|
|
794
|
+
cols[name] = g.mask_where(cells)
|
|
795
|
+
else
|
|
796
|
+
cols[name] = g.copy
|
|
797
|
+
end
|
|
699
798
|
end
|
|
700
799
|
# The index is copied along with the columns. Its values are carried over
|
|
701
800
|
# unmasked (an undetermined row keeps its label, as mask_rows leaves the
|