carray 3.0.1 → 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 +824 -0
- data/README.md +5 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +258 -4
- data/ext/ca_axis_group.c +368 -184
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +90 -41
- 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 +960 -245
- data/ext/ca_kernel_iterator.h +352 -181
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +194 -42
- 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 +5 -4
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +180 -50
- data/ext/ca_obj_face.c +428 -62
- data/ext/ca_obj_face.h +107 -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 +39 -26
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +235 -55
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +551 -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 +88 -75
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -123
- data/ext/ca_obj_stride.c +267 -35
- data/ext/ca_obj_string.c +25 -22
- 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 +157 -29
- data/ext/ca_op_ipower.c +25 -13
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +385 -198
- data/ext/ca_sweep_engine.h +31 -23
- 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 +90 -28
- data/ext/carray_access.c +487 -231
- data/ext/carray_address_basis.c +731 -0
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +49 -17
- data/ext/carray_call_cfunc.c +667 -511
- data/ext/carray_cast.c +1556 -247
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +84 -38
- data/ext/carray_core.c +591 -119
- data/ext/carray_count.c +148 -96
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +604 -291
- 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 +400 -1
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +88 -66
- data/ext/carray_mask.c +705 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +263 -63
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1093 -363
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +582 -204
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +183 -70
- data/ext/carray_sort.c +235 -240
- 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 +103 -118
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1349 -851
- data/ext/ruby_carray.c +17 -2
- 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 +24 -3
- data/lib/carray/autoload_method_extension.rb +18 -0
- data/lib/carray/axis_group.rb +245 -20
- data/lib/carray/basics.rb +95 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +200 -54
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +244 -59
- data/lib/carray/categorical_iterator.rb +391 -757
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +177 -27
- data/lib/carray/construct.rb +40 -0
- data/lib/carray/data_type_extension.rb +3 -0
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +590 -76
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +205 -35
- data/lib/carray/frame/group.rb +199 -40
- data/lib/carray/frame/io.rb +452 -60
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +54 -19
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +726 -56
- 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 +71 -20
- data/lib/carray/iterator.rb +157 -2
- data/lib/carray/lazy.rb +54 -67
- data/lib/carray/mask_gap_fill.rb +66 -50
- 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/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- 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 +130 -6
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +235 -21
- data/lib/carray/methods/mask_duplicates.rb +35 -1
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +22 -1
- data/lib/carray/methods/repeat.rb +91 -0
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +52 -3
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +86 -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 +109 -40
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +9 -4
- 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 +535 -105
- data/lib/carray/window_iterator.rb +259 -82
- data/lib/carray.rb +4 -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_block.rb +2 -7
- 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 +11 -4
- data/yard-stubs/carray_access.rb +6 -5
- data/yard-stubs/carray_broadcast.rb +2 -2
- 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 -83
- data/yard-stubs/carray_count.rb +16 -2
- data/yard-stubs/carray_lazy.rb +210 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +1499 -4
- data/yard-stubs/carray_median_percentile.rb +26 -8
- data/yard-stubs/carray_order.rb +43 -82
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +11 -8
- data/yard-stubs/carray_sort.rb +50 -8
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -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 -25837
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14593
- data/ext/carray_kernels_reduce_extreme.c +0 -16948
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32138
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
data/lib/carray/frame/group.rb
CHANGED
|
@@ -13,7 +13,67 @@ class CAFrame
|
|
|
13
13
|
raise ArgumentError, "group_by needs at least one key" if keys.empty?
|
|
14
14
|
cat = grouping_categorical(keys)
|
|
15
15
|
axis = keys.size == 1 && keys.first.is_a?(String) ? keys.first : "group"
|
|
16
|
-
|
|
16
|
+
key = keys.size == 1 ? key_column(keys.first) : nil
|
|
17
|
+
GroupedFrame.new(self, cat, axis, keys.grep(String), key: key)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Group rows into time bins of length +unit+ along the time column (or the
|
|
21
|
+
# time index) +name+, for the reductions GroupedFrame offers:
|
|
22
|
+
#
|
|
23
|
+
# df.resample("time", "1 hour").mean
|
|
24
|
+
# df.resample("time", "1 day", fill: true).aggregate("rain" => ["rain", :sum])
|
|
25
|
+
#
|
|
26
|
+
# The result's index is the bin labels as a CATime, in time order, and its
|
|
27
|
+
# row axis is named after +name+.
|
|
28
|
+
#
|
|
29
|
+
# +label: :left+ (the default) makes each bin start at its label and hold
|
|
30
|
+
# the times at or after it: [00:00, 01:00) is labelled 00:00. +label: :right+
|
|
31
|
+
# makes it end at its label and hold the times up to and including it:
|
|
32
|
+
# (00:00, 01:00] is labelled 01:00, the convention for a value that
|
|
33
|
+
# describes the hour before it. +origin:+ shifts the bins as +CATime#floor+
|
|
34
|
+
# does ("1 hour" with origin 00:30 gives 00:30, 01:30, ...).
|
|
35
|
+
#
|
|
36
|
+
# Without +fill:+, a bin with no rows does not appear. With +fill: true+
|
|
37
|
+
# every bin from the first to the last is a row, and an empty one reduces
|
|
38
|
+
# as an empty reduction does: UNDEF for +mean+, 0 for +count+ and +sum+.
|
|
39
|
+
# A row whose time is masked belongs to no bin.
|
|
40
|
+
#
|
|
41
|
+
# @param name [String] the time column, or the index's axis name.
|
|
42
|
+
# @param unit [String, Symbol, CATime::Resolution] the bin length.
|
|
43
|
+
# @param origin [nil, Time, String, CATime::Element] where bins start.
|
|
44
|
+
# @param label [:left, :right] which end of a bin names it and is closed.
|
|
45
|
+
# @param fill [Boolean] whether bins with no rows are rows of the result.
|
|
46
|
+
# @return [GroupedFrame]
|
|
47
|
+
# @raise [ArgumentError] when the column is not a CATime, or +label:+ is
|
|
48
|
+
# neither :left nor :right.
|
|
49
|
+
def resample(name, unit, origin: nil, label: :left, fill: false)
|
|
50
|
+
name = name.to_s
|
|
51
|
+
time = column_or_index(name)
|
|
52
|
+
unless time.is_a?(CATime)
|
|
53
|
+
raise ArgumentError, "resample: #{name.inspect} is not a time column (got #{time.class})"
|
|
54
|
+
end
|
|
55
|
+
bins =
|
|
56
|
+
case label
|
|
57
|
+
when :left then time.floor(unit: unit, origin: origin)
|
|
58
|
+
when :right then time.ceil(unit: unit, origin: origin)
|
|
59
|
+
else
|
|
60
|
+
raise ArgumentError, "resample: label: must be :left or :right (got #{label.inspect})"
|
|
61
|
+
end
|
|
62
|
+
grid = fill && bins.count_not_masked > 0 ? resample_grid(bins, unit) : bins.unique(sort: true)
|
|
63
|
+
cat = CACategorical.from_codes(bins.locate_addr(grid), grid.to_a)
|
|
64
|
+
GroupedFrame.new(self, cat, name, @columns.key?(name) ? [name] : [], index: grid)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Every bin from the first occupied one to the last. A fixed-length bin
|
|
68
|
+
# steps from the first bin, so an origin's phase is kept; a month or a year
|
|
69
|
+
# cannot be stepped on a finer grid and is laid on its own unit, where
|
|
70
|
+
# bins start on the calendar boundary anyway.
|
|
71
|
+
private def resample_grid(bins, unit)
|
|
72
|
+
if %i[Y M].include?(CATime::Resolution.parse(unit).base)
|
|
73
|
+
CArray.time_range(bins.min, bins.max, unit: unit)
|
|
74
|
+
else
|
|
75
|
+
CArray.time_range(bins.min, bins.max, unit: bins.unit, step: unit)
|
|
76
|
+
end
|
|
17
77
|
end
|
|
18
78
|
|
|
19
79
|
# Number of rows currently selected — used by group per-group view-frames
|
|
@@ -24,27 +84,58 @@ class CAFrame
|
|
|
24
84
|
if cols.size == 1
|
|
25
85
|
cols.first.categorize
|
|
26
86
|
else
|
|
27
|
-
|
|
28
|
-
# values, categorized by content (the codes are composed from the
|
|
29
|
-
# per-column keys).
|
|
30
|
-
n = nrow
|
|
31
|
-
CArray.object(n) { |i| cols.map { |c| c[i] } }.categorize
|
|
87
|
+
composite_categorical(cols)
|
|
32
88
|
end
|
|
33
89
|
end
|
|
34
90
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
91
|
+
# A composite key. Each column is categorized on its own, so a component
|
|
92
|
+
# compares exactly as a single key does (NaN is one value, -0.0 is 0.0, a
|
|
93
|
+
# masked cell matches nothing); a row's group is then the tuple of its
|
|
94
|
+
# component codes, and a group's label the tuple of the component labels.
|
|
95
|
+
# One masked component leaves the row in no group, as a masked single key.
|
|
96
|
+
private def composite_categorical(cols)
|
|
97
|
+
cats = cols.map(&:categorize)
|
|
98
|
+
sizes = cats.map { |c| c.labels.size }
|
|
99
|
+
tuple = if sizes.inject(1, :*) <= 2**62
|
|
100
|
+
cats.drop(1).inject(cats[0].codes.int64) { |acc, c| acc * c.labels.size + c.codes.int64 }
|
|
101
|
+
else
|
|
102
|
+
codes = cats.map(&:codes)
|
|
103
|
+
t = CArray.object(nrow) { |i| codes.map { |c| c[i] } }
|
|
104
|
+
nrow.times { |i| t[i] = UNDEF if t[i].any? { |x| UNDEF.equal?(x) } }
|
|
105
|
+
t
|
|
106
|
+
end
|
|
107
|
+
rows = tuple.categorize
|
|
108
|
+
labels = rows.labels.map do |code|
|
|
109
|
+
parts = code.is_a?(Array) ? code : sizes.reverse.map { |n| code, d = code.divmod(n); d }.reverse
|
|
110
|
+
parts.each_with_index.map { |d, j| cats[j].labels[d] }
|
|
111
|
+
end
|
|
112
|
+
CACategorical.from_codes(rows.codes, labels)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# The column a group key names, checked: +verb+ is the method the caller
|
|
116
|
+
# called, for the messages.
|
|
117
|
+
private def key_column(key, verb = "group_by")
|
|
118
|
+
col = case key
|
|
119
|
+
when String
|
|
120
|
+
@columns.fetch(key) { raise KeyError, "#{verb}: no column #{key.inspect}" }
|
|
121
|
+
when CArray
|
|
122
|
+
unless key.shape[0] == nrow
|
|
123
|
+
raise ArgumentError,
|
|
124
|
+
"#{verb}: the key has #{key.shape[0]} values for #{nrow} rows"
|
|
125
|
+
end
|
|
126
|
+
key
|
|
127
|
+
else
|
|
128
|
+
raise ArgumentError, "#{verb}: a group key is a column name or a CArray (got #{key.class})"
|
|
129
|
+
end
|
|
130
|
+
# A key gives each row one value. An N-D one would be categorized cell
|
|
131
|
+
# by cell, making groups of cells rather than of rows.
|
|
132
|
+
unless col.ndim == 1
|
|
133
|
+
name = key.is_a?(String) ? "#{key.inspect} " : ""
|
|
134
|
+
raise ArgumentError,
|
|
135
|
+
"#{verb}: the key #{name}has shape #{col.shape.inspect}, not one value per row; " \
|
|
136
|
+
"group by one of its components (e.g. df[name][nil, 0])"
|
|
47
137
|
end
|
|
138
|
+
col
|
|
48
139
|
end
|
|
49
140
|
end
|
|
50
141
|
|
|
@@ -56,11 +147,24 @@ end
|
|
|
56
147
|
# aggregate -> declarative per-column reductions into a new frame
|
|
57
148
|
# table { |g| }-> cross-column Ruby escape, g is a per-group view-frame
|
|
58
149
|
class GroupedFrame
|
|
59
|
-
|
|
150
|
+
# +key_names+ are the frame columns the grouping was keyed on. They become
|
|
151
|
+
# the result's index, so the reduction shortcuts must not also return them as
|
|
152
|
+
# reduced columns; an external CArray key contributes no name.
|
|
153
|
+
#
|
|
154
|
+
# The result's index is +index:+ when given (one entry per group, in code
|
|
155
|
+
# order); otherwise, for a single +key:+, the key's value at each group's
|
|
156
|
+
# first row, so it keeps the key's data type and Face; otherwise an object
|
|
157
|
+
# array of the labels (a composite key's tuples).
|
|
158
|
+
#
|
|
159
|
+
# The grouping and the index are taken here, so a later change to the key
|
|
160
|
+
# does not rename the groups; the values are read when a reduction is.
|
|
161
|
+
def initialize(frame, cat, axis_name, key_names = [], index: nil, key: nil)
|
|
60
162
|
@frame = frame
|
|
61
163
|
@cat = cat
|
|
62
164
|
@axis_name = axis_name
|
|
165
|
+
@key_names = key_names
|
|
63
166
|
@labels = cat.labels # group values, in code order
|
|
167
|
+
@index = index || (key && key.project(group_perm[group_bounds[0...ngroup]]))
|
|
64
168
|
end
|
|
65
169
|
|
|
66
170
|
# Number of groups.
|
|
@@ -81,16 +185,22 @@ class GroupedFrame
|
|
|
81
185
|
# Declarative aggregation (memo §11.6). Spec maps an output column name to
|
|
82
186
|
# +[input_column, reduction]+, where reduction is a Symbol (vectorized,
|
|
83
187
|
# applied through the group iterator) or a Proc (per-group custom, called
|
|
84
|
-
# with the group's column slice).
|
|
188
|
+
# with the group's column slice). A Symbol reduction takes its arguments
|
|
189
|
+
# after it, as in a call: +["temp", :percentile, 90.0]+, and its keywords
|
|
190
|
+
# last: +["temp", :mean, min_count: 6]+.
|
|
85
191
|
def aggregate(spec)
|
|
86
192
|
cols = {}
|
|
87
|
-
spec.each do |out_name, (in_name, reduction)|
|
|
193
|
+
spec.each do |out_name, (in_name, reduction, *args)|
|
|
88
194
|
out = out_name.to_s
|
|
195
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
89
196
|
cols[out] =
|
|
90
197
|
case reduction
|
|
91
198
|
when Symbol
|
|
92
|
-
|
|
199
|
+
group_reduce(@frame[in_name], reduction, *args, **options)
|
|
93
200
|
when Proc
|
|
201
|
+
unless args.empty? && options.empty?
|
|
202
|
+
raise ArgumentError, "aggregate: a Proc reduction takes no arguments (#{out.inspect})"
|
|
203
|
+
end
|
|
94
204
|
per_group_column(in_name, reduction)
|
|
95
205
|
else
|
|
96
206
|
raise ArgumentError,
|
|
@@ -122,10 +232,36 @@ class GroupedFrame
|
|
|
122
232
|
CAFrame.new(cols, axis_name: @axis_name, index: label_index)
|
|
123
233
|
end
|
|
124
234
|
|
|
125
|
-
# Convenience reductions over every
|
|
126
|
-
#
|
|
235
|
+
# Convenience reductions over every column that defines the reduction. A
|
|
236
|
+
# column is in the result when its type answers the reduction -- a time
|
|
237
|
+
# column has a mean, a minimum and a maximum but no sum, a string column a
|
|
238
|
+
# minimum and a maximum only -- and left out when it does not, as are the
|
|
239
|
+
# key columns. An N-D column is reduced along the rows, so each group keeps
|
|
240
|
+
# its trailing shape: a (N, L) column gives (groups, L). Each
|
|
241
|
+
# takes +min_count:+ and +fill_value:+ as a core reduction does: a group
|
|
242
|
+
# with fewer than min_count present values is UNDEF, and fill_value fills
|
|
243
|
+
# the UNDEF cells.
|
|
244
|
+
#
|
|
245
|
+
# @!method sum(min_count: nil, fill_value: nil)
|
|
246
|
+
# Returns a frame of the per-group sum of every column whose type has a
|
|
247
|
+
# sum, an N-D one per group along the rows. Other columns are left out.
|
|
248
|
+
# @return [CAFrame] one row per group, indexed by the group labels.
|
|
249
|
+
# @!method mean(min_count: nil, fill_value: nil)
|
|
250
|
+
# Returns a frame of the per-group mean of every column whose type has
|
|
251
|
+
# a mean, as {#sum} does.
|
|
252
|
+
# @return [CAFrame] one row per group.
|
|
253
|
+
# @!method min(min_count: nil, fill_value: nil)
|
|
254
|
+
# Returns a frame of the per-group minimum of every column whose type has
|
|
255
|
+
# a minimum, as {#sum} does.
|
|
256
|
+
# @return [CAFrame] one row per group.
|
|
257
|
+
# @!method max(min_count: nil, fill_value: nil)
|
|
258
|
+
# Returns a frame of the per-group maximum of every column whose type has
|
|
259
|
+
# a maximum, as {#sum} does.
|
|
260
|
+
# @return [CAFrame] one row per group.
|
|
127
261
|
[:sum, :mean, :min, :max].each do |red|
|
|
128
|
-
define_method(red)
|
|
262
|
+
define_method(red) do |min_count: nil, fill_value: nil|
|
|
263
|
+
reduce_columns(red, min_count: min_count, fill_value: fill_value)
|
|
264
|
+
end
|
|
129
265
|
end
|
|
130
266
|
|
|
131
267
|
# @return [String]
|
|
@@ -133,23 +269,47 @@ class GroupedFrame
|
|
|
133
269
|
"#<GroupedFrame ngroup=#{ngroup} by=#{@axis_name.inspect}>"
|
|
134
270
|
end
|
|
135
271
|
|
|
272
|
+
# Each result gets its own index.
|
|
136
273
|
private def label_index
|
|
274
|
+
return @index.copy if @index
|
|
137
275
|
CArray.object(@labels.size) { |i| @labels[i] }
|
|
138
276
|
end
|
|
139
277
|
|
|
140
|
-
|
|
141
|
-
private_constant :NON_NUMERIC
|
|
142
|
-
|
|
143
|
-
private def reduce_numeric(reduction)
|
|
278
|
+
private def reduce_columns(reduction, **keywords)
|
|
144
279
|
cols = {}
|
|
145
|
-
@frame.
|
|
280
|
+
@frame.column_names.each do |name|
|
|
281
|
+
# A key column is the index here, not a result column.
|
|
282
|
+
next if @key_names.include?(name)
|
|
146
283
|
col = @frame[name]
|
|
147
|
-
next unless
|
|
148
|
-
cols[name] = col
|
|
284
|
+
next unless answers?(col, reduction)
|
|
285
|
+
cols[name] = group_reduce(col, reduction, **keywords)
|
|
149
286
|
end
|
|
150
287
|
CAFrame.new(cols, axis_name: @axis_name, index: label_index)
|
|
151
288
|
end
|
|
152
289
|
|
|
290
|
+
# A column's group reduction. An N-D column is reduced along the rows, so
|
|
291
|
+
# each group keeps the trailing shape: a profile column (N, L) gives (G, L).
|
|
292
|
+
private def group_reduce(col, reduction, *args, **keywords)
|
|
293
|
+
it = col.group_by_category(@cat)
|
|
294
|
+
if col.ndim > 1 && !keywords.key?(:axis)
|
|
295
|
+
it.public_send(reduction, *args, axis: 0, **keywords)
|
|
296
|
+
else
|
|
297
|
+
it.public_send(reduction, *args, **keywords)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Whether the column's type defines the reduction, asked of the core on one
|
|
302
|
+
# cell of it so that the answer is about the type and not the data: a
|
|
303
|
+
# refusal there is the core saying the reduction does not apply, while an
|
|
304
|
+
# error in the grouped reduction itself is left to raise. A column with no
|
|
305
|
+
# rows is asked as it is: the refusal does not depend on the cells.
|
|
306
|
+
private def answers?(col, reduction)
|
|
307
|
+
(col.elements > 0 ? col[0..0] : col).public_send(reduction)
|
|
308
|
+
true
|
|
309
|
+
rescue CArray::DataTypeError, TypeError, ArgumentError
|
|
310
|
+
false
|
|
311
|
+
end
|
|
312
|
+
|
|
153
313
|
private def per_group_column(in_name, proc)
|
|
154
314
|
results = []
|
|
155
315
|
each_group_slice(in_name) { |slice| results << proc.call(slice) }
|
|
@@ -168,19 +328,18 @@ class GroupedFrame
|
|
|
168
328
|
end
|
|
169
329
|
|
|
170
330
|
private def group_address(k)
|
|
171
|
-
|
|
172
|
-
group_perm[start...(start + group_sizes[k])]
|
|
331
|
+
group_perm[group_bounds[k]...group_bounds[k + 1]]
|
|
173
332
|
end
|
|
174
333
|
|
|
175
|
-
private def
|
|
176
|
-
@
|
|
334
|
+
private def group_codes
|
|
335
|
+
@cat.codes
|
|
177
336
|
end
|
|
178
337
|
|
|
179
|
-
private def
|
|
180
|
-
@
|
|
338
|
+
private def group_perm
|
|
339
|
+
@group_perm ||= @cat.sort_addr
|
|
181
340
|
end
|
|
182
341
|
|
|
183
|
-
private def
|
|
184
|
-
@
|
|
342
|
+
private def group_bounds
|
|
343
|
+
@group_bounds ||= CArray.segment_offsets(lengths: @cat.category_sizes)
|
|
185
344
|
end
|
|
186
345
|
end
|