carray 3.0.4 → 3.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +438 -0
- data/README.md +1 -1
- data/carray.gemspec +3 -2
- data/ext/ca_array_pool.c +25 -1
- data/ext/ca_axis_dispatch.c +19 -20
- data/ext/ca_axis_group.c +1 -1
- data/ext/ca_categorical_iterator.c +34 -27
- data/ext/ca_compare.c +4 -4
- data/ext/ca_composite_dispatch.c +9 -9
- data/ext/ca_kernel_iterator.c +122 -237
- data/ext/ca_kernel_iterator.h +522 -337
- data/ext/ca_obj_array.c +69 -19
- data/ext/ca_obj_bincmp.c +10 -1
- data/ext/ca_obj_binop.c +11 -1
- data/ext/ca_obj_bitarray.c +5 -1
- data/ext/ca_obj_bitfield.c +60 -43
- data/ext/ca_obj_byte_swap.c +4 -1
- data/ext/ca_obj_const_string.c +15 -3
- data/ext/ca_obj_face.c +44 -1
- data/ext/ca_obj_fake.c +3 -1
- data/ext/ca_obj_field.c +13 -3
- data/ext/ca_obj_grid.c +219 -17
- data/ext/ca_obj_meld.c +69 -113
- data/ext/ca_obj_moncmp.c +9 -1
- data/ext/ca_obj_monop.c +86 -12
- data/ext/ca_obj_object.c +118 -32
- data/ext/ca_obj_record.c +3 -0
- data/ext/ca_obj_reduce.c +19 -1
- data/ext/ca_obj_refer.c +80 -26
- data/ext/ca_obj_remap.c +69 -10
- data/ext/ca_obj_roll.c +27 -6
- data/ext/ca_obj_select.c +38 -6
- data/ext/ca_obj_select_axis.c +1 -0
- data/ext/ca_obj_shift.c +32 -7
- data/ext/ca_obj_stack.c +311 -111
- data/ext/ca_obj_stride.c +159 -64
- data/ext/ca_obj_string.c +27 -2
- data/ext/ca_obj_tile.c +28 -7
- data/ext/ca_obj_transpose.c +8 -4
- data/ext/ca_obj_triop.c +12 -1
- data/ext/ca_obj_window.c +80 -50
- data/ext/ca_op_ipower.c +12 -12
- data/ext/ca_sort_kernels.h +6 -3
- data/ext/caframe_csv_reader.c +1 -1
- data/ext/carray.h +35 -1
- data/ext/carray_access.c +43 -67
- data/ext/carray_address_basis.c +134 -0
- data/ext/carray_attribute.c +32 -0
- data/ext/carray_broadcast.c +3 -4
- data/ext/carray_build_flags.h +1 -1
- data/ext/carray_cast.c +251 -93
- data/ext/carray_cast_func.rb +56 -26
- data/ext/carray_conversion.c +11 -7
- data/ext/carray_core.c +814 -72
- data/ext/carray_count.c +6 -0
- data/ext/carray_element.c +22 -13
- data/ext/carray_factorize.c +20 -20
- data/ext/carray_generate.c +8 -2
- data/ext/carray_histogram.c +26 -4
- data/ext/carray_index_classifier.c +4 -4
- data/ext/carray_internal.h +159 -12
- data/ext/carray_kernels_bincmp.c +2174 -170
- data/ext/carray_kernels_binop.c +25653 -5487
- data/ext/carray_kernels_init.c +8 -8
- data/ext/carray_kernels_moncmp.c +96 -96
- data/ext/carray_kernels_monop.c +12253 -5235
- data/ext/carray_kernels_reduce_aggregate.c +6150 -2423
- data/ext/carray_kernels_reduce_boolean.c +1533 -15
- data/ext/carray_kernels_reduce_cumulative.c +2442 -882
- data/ext/carray_kernels_reduce_extreme.c +5658 -1661
- data/ext/carray_kernels_reduce_variance.c +2534 -402
- data/ext/carray_kernels_scan.c +368 -368
- data/ext/carray_kernels_search.c +4551 -604
- data/ext/carray_kernels_sort.c +659 -751
- data/ext/carray_kernels_triop.c +114 -114
- data/ext/carray_key_windows.c +4 -0
- data/ext/carray_lazy.c +47 -0
- data/ext/carray_loop.c +1 -1
- data/ext/carray_mask.c +175 -161
- data/ext/carray_median_percentile.c +4 -4
- data/ext/carray_memory_view.c +15 -9
- data/ext/carray_operator.c +152 -49
- data/ext/carray_order.c +230 -10
- data/ext/carray_random.c +22 -19
- data/ext/carray_scatter.c +24 -7
- data/ext/carray_slab.c +128 -29
- data/ext/carray_sort.c +224 -16
- data/ext/carray_sort_kernel.c +5 -7
- data/ext/carray_struct.c +35 -43
- data/ext/carray_test.c +74 -19
- data/ext/carray_time_text.c +6 -4
- data/ext/carray_utils.c +63 -21
- data/ext/extconf.rb +11 -0
- data/ext/mkkernel.rb +546 -199
- data/ext/ruby_carray.c +4 -1
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +42 -5
- data/lib/carray/autoload_carray.rb +3 -3
- data/lib/carray/autoload_method_extension.rb +5 -3
- data/lib/carray/axis_group.rb +13 -7
- data/lib/carray/basics.rb +28 -8
- data/lib/carray/block_iterator.rb +17 -3
- data/lib/carray/categorical.rb +35 -23
- data/lib/carray/categorical_iterator.rb +8 -8
- data/lib/carray/complex.rb +2 -2
- data/lib/carray/conditional.rb +3 -2
- data/lib/carray/const_string.rb +10 -0
- data/lib/carray/data_type_extension.rb +8 -1
- data/lib/carray/fixlen_string.rb +41 -2
- data/lib/carray/frame/column_verbs.rb +48 -16
- data/lib/carray/frame/concat.rb +17 -3
- data/lib/carray/frame/convert.rb +19 -0
- data/lib/carray/frame/csv_parser.rb +32 -32
- data/lib/carray/frame/describe.rb +4 -2
- data/lib/carray/frame/frame.rb +405 -85
- data/lib/carray/frame/group.rb +777 -64
- data/lib/carray/frame/io.rb +212 -72
- data/lib/carray/frame/join.rb +146 -37
- data/lib/carray/frame/records.rb +3 -3
- data/lib/carray/frame/reshape.rb +89 -11
- data/lib/carray/frame/sort.rb +57 -9
- data/lib/carray/frame/verbs.rb +384 -49
- data/lib/carray/frame/windows.rb +44 -6
- data/lib/carray/fuse_source.rb +31 -2
- data/lib/carray/inspect.rb +7 -2
- data/lib/carray/iterator.rb +32 -0
- data/lib/carray/lazy.rb +52 -8
- data/lib/carray/mask_gap_fill.rb +37 -17
- data/lib/carray/meld_reduce.rb +1 -1
- data/lib/carray/methods/choose.rb +3 -2
- data/lib/carray/methods/is_in.rb +8 -1
- data/lib/carray/methods/meshgrid.rb +3 -1
- data/lib/carray/runtime.rb +47 -8
- data/lib/carray/segment_iterator.rb +35 -16
- data/lib/carray/serialize.rb +53 -19
- data/lib/carray/stack.rb +33 -18
- data/lib/carray/string.rb +8 -0
- data/lib/carray/string_operation_extension.rb +13 -5
- data/lib/carray/struct_builder.rb +5 -3
- data/lib/carray/time.rb +112 -68
- data/lib/carray/window_iterator.rb +16 -4
- data/yard-stubs/ca_obj_array.rb +26 -1
- data/yard-stubs/ca_obj_transpose.rb +5 -4
- data/yard-stubs/carray_attribute.rb +3 -1
- data/yard-stubs/carray_cast.rb +10 -5
- data/yard-stubs/carray_conversion.rb +13 -3
- data/yard-stubs/carray_element.rb +7 -6
- data/yard-stubs/carray_mask.rb +28 -4
- data/yard-stubs/carray_math.rb +45 -20
- data/yard-stubs/carray_memory_view.rb +7 -0
- data/yard-stubs/carray_order.rb +100 -4
- data/yard-stubs/carray_scatter.rb +7 -5
- data/yard-stubs/carray_test.rb +7 -4
- metadata +18 -4
data/lib/carray/frame/group.rb
CHANGED
|
@@ -6,7 +6,57 @@
|
|
|
6
6
|
# -> group_by_category, so the frame layer only builds the key and hands
|
|
7
7
|
# the group iterator back (exposure, §4.3).
|
|
8
8
|
|
|
9
|
+
require "carray/distance_limit"
|
|
10
|
+
|
|
9
11
|
class CAFrame
|
|
12
|
+
# How many rows hold each value of a column, or each combination of the
|
|
13
|
+
# values of several (every column by default), as a frame: the values as
|
|
14
|
+
# its index (one column) or its first columns (several), and their counts
|
|
15
|
+
# in a column +name:+ ("count"). +sort:+ orders the rows by +:count+, the
|
|
16
|
+
# most first (the default; ties in the order the values first appear), by
|
|
17
|
+
# +:value+, or not at all (+false+, the order the values first appear). A
|
|
18
|
+
# row with a masked value is not counted, as +group_by+ leaves it out.
|
|
19
|
+
#
|
|
20
|
+
# df.value_counts("station")
|
|
21
|
+
# df.value_counts("station", "weather", sort: :value)
|
|
22
|
+
#
|
|
23
|
+
# The columns are given as +select+ takes them. +group_by(...).size+ is the
|
|
24
|
+
# same count in the order of the groups.
|
|
25
|
+
#
|
|
26
|
+
# @return [CAFrame]
|
|
27
|
+
# @raise [ArgumentError] for another +sort:+, or a +name:+ a key has.
|
|
28
|
+
def value_counts (*names, sort: :count, name: "count")
|
|
29
|
+
unless [:count, :value, false].include?(sort)
|
|
30
|
+
raise ArgumentError, "value_counts: sort: is :count, :value or false (got #{sort.inspect})"
|
|
31
|
+
end
|
|
32
|
+
keys = names.empty? ? column_names : names_selected(names, "value_counts")
|
|
33
|
+
if keys.empty?
|
|
34
|
+
hint = @index ? "; name the index (#{@axis_name.inspect}) to count its values" : ""
|
|
35
|
+
raise ArgumentError, "value_counts: the frame has no columns#{hint}"
|
|
36
|
+
end
|
|
37
|
+
name = column_name_arg(name, "value_counts")
|
|
38
|
+
if keys.include?(name)
|
|
39
|
+
raise ArgumentError,
|
|
40
|
+
"value_counts: a counted column is named #{name.inspect}; name the count " \
|
|
41
|
+
"otherwise, as in value_counts(..., name: \"n\")"
|
|
42
|
+
end
|
|
43
|
+
out = group_by(*keys).size(name: name)
|
|
44
|
+
case sort
|
|
45
|
+
when :count then out.sort_by_key(name, descending: true)
|
|
46
|
+
when :value
|
|
47
|
+
# A categorical key is ordered by its labels, as CArray#value_counts
|
|
48
|
+
# orders one; its codes are in the order labels were met.
|
|
49
|
+
cols = keys.map do |key|
|
|
50
|
+
col = out.__send__(:key_column, key, "value_counts")
|
|
51
|
+
col.is_a?(CACategorical) ? col.to_type(:object) : col
|
|
52
|
+
end
|
|
53
|
+
out.__send__(:select_rows, CArray.sort_addr(*cols))
|
|
54
|
+
else out
|
|
55
|
+
end
|
|
56
|
+
rescue ArgumentError, KeyError, TypeError, CArray::DataTypeError => e
|
|
57
|
+
raise e.class, e.message.sub(/\A(group_by|size|sort_by_key):/, "value_counts:"), e.backtrace
|
|
58
|
+
end
|
|
59
|
+
|
|
10
60
|
# Group rows by one or more keys. Each key is a column name (String) or an
|
|
11
61
|
# external length-N CArray. Returns a GroupedFrame.
|
|
12
62
|
#
|
|
@@ -158,7 +208,7 @@ class CAFrame
|
|
|
158
208
|
g_of = nb > 0 ? occupied / nb : occupied
|
|
159
209
|
b_of = nb > 0 ? occupied % nb : occupied
|
|
160
210
|
# Each group's by values, from its first row.
|
|
161
|
-
order = gcat.
|
|
211
|
+
order = gcat.group_addr
|
|
162
212
|
first = order[CArray.segment_offsets(lengths: gcat.category_sizes)[0...ng]]
|
|
163
213
|
columns = {}
|
|
164
214
|
by_names.zip(by_cols).each do |n, col|
|
|
@@ -234,15 +284,358 @@ class CAFrame
|
|
|
234
284
|
end
|
|
235
285
|
col
|
|
236
286
|
end
|
|
287
|
+
|
|
288
|
+
# Each column reduced over all the rows, as a frame of one row: the
|
|
289
|
+
# reductions of +GroupedFrame+ with every row in one group. Given names, the
|
|
290
|
+
# frame of those columns; without, of every column whose type has the
|
|
291
|
+
# reduction. A column keeps its type where the reduction keeps it (the
|
|
292
|
+
# earliest time of a time column is a time), and +min_count:+ /
|
|
293
|
+
# +fill_value:+ / +keep_rows:+ work as they do for a group. A frame with no
|
|
294
|
+
# rows still gives its one row: a sum of nothing is 0, a mean UNDEF.
|
|
295
|
+
#
|
|
296
|
+
# df.mean # => one row, the mean of every numeric column
|
|
297
|
+
# df.max("tmax", "tmin") # => one row, the two maxima
|
|
298
|
+
# df["tmax"] - df.mean("tmax", keep_rows: true) # the anomaly on each row
|
|
299
|
+
#
|
|
300
|
+
# For one column's value as a Ruby value, reduce the column: +df["tmax"].mean+.
|
|
301
|
+
#
|
|
302
|
+
# @!method sum(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
303
|
+
# @return [CAFrame] one row.
|
|
304
|
+
# @!method mean(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
305
|
+
# @return [CAFrame] one row.
|
|
306
|
+
# @!method min(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
307
|
+
# @return [CAFrame] one row.
|
|
308
|
+
# @!method max(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
309
|
+
# @return [CAFrame] one row.
|
|
310
|
+
# @!method prod(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
311
|
+
# @return [CAFrame] one row.
|
|
312
|
+
# @!method accumulate(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
313
|
+
# @return [CAFrame] one row.
|
|
314
|
+
# @!method median(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
315
|
+
# @return [CAFrame] one row.
|
|
316
|
+
# @!method variance(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
317
|
+
# @return [CAFrame] one row.
|
|
318
|
+
# @!method stddev(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
319
|
+
# @return [CAFrame] one row.
|
|
320
|
+
# @!method variancep(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
321
|
+
# @return [CAFrame] one row.
|
|
322
|
+
# @!method stddevp(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
323
|
+
# @return [CAFrame] one row.
|
|
324
|
+
# @!method first(*names, keep_rows: false)
|
|
325
|
+
# @return [CAFrame] one row: each column's first present value.
|
|
326
|
+
# @!method last(*names, keep_rows: false)
|
|
327
|
+
# @return [CAFrame] one row: each column's last present value.
|
|
328
|
+
# @!method nunique(*names, keep_rows: false)
|
|
329
|
+
# @return [CAFrame] one row: each column's number of distinct present values.
|
|
330
|
+
# @!method count(*names, keep_rows: false)
|
|
331
|
+
# @return [CAFrame] one row: each column's number of present values.
|
|
332
|
+
# @!method all(*names, keep_rows: false)
|
|
333
|
+
# @return [CAFrame] one row, for the boolean columns.
|
|
334
|
+
# @!method any(*names, keep_rows: false)
|
|
335
|
+
# @return [CAFrame] one row, for the boolean columns.
|
|
336
|
+
CArray.send(:define_named_methods, self,
|
|
337
|
+
[:sum, :mean, :min, :max, :prod, :accumulate, :median,
|
|
338
|
+
:variance, :stddev, :variancep, :stddevp], :reduce_all_rows)
|
|
339
|
+
|
|
340
|
+
CArray.send(:define_named_methods, self,
|
|
341
|
+
[:first, :last, :nunique, :count, :all, :any], :reduce_all_rows_to_a_value)
|
|
342
|
+
|
|
343
|
+
# Each column's +p+-th percentile (0 to 100) over all the rows, as a frame
|
|
344
|
+
# of one row, as {#median} gives the 50th. The percentage comes first, then
|
|
345
|
+
# the names.
|
|
346
|
+
#
|
|
347
|
+
# df.percentile(90, "revenue")
|
|
348
|
+
#
|
|
349
|
+
# @param p [Numeric] the percentage.
|
|
350
|
+
# @param names [Array<String>] the columns; default every column that has
|
|
351
|
+
# a percentile.
|
|
352
|
+
# @return [CAFrame, CArray] one row; with +keep_rows: true+ the frame's
|
|
353
|
+
# rows, one name giving the CArray.
|
|
354
|
+
def percentile (p, *names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
355
|
+
CAFrame.__send__(:check_percentage, p)
|
|
356
|
+
reduce_all_rows(:percentile, *names, min_count: min_count, fill_value: fill_value,
|
|
357
|
+
keep_rows: keep_rows, args: [p])
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Each column's five quartile percentiles over all the rows, as
|
|
361
|
+
# +CArray#quantile+ gives them, as a frame of one row: a column "x" gives
|
|
362
|
+
# the five columns "x_p0", "x_p25", "x_p50", "x_p75" and "x_p100". With
|
|
363
|
+
# +keep_rows: true+ every row holds them.
|
|
364
|
+
#
|
|
365
|
+
# df.quantile("temp")
|
|
366
|
+
#
|
|
367
|
+
# @param names [Array<String>] the columns; default every column that has
|
|
368
|
+
# a quantile.
|
|
369
|
+
# @return [CAFrame]
|
|
370
|
+
def quantile (*names, keep_rows: false)
|
|
371
|
+
named = {}
|
|
372
|
+
ColumnWalk.new(self).over(:quantile, names) do |col, name|
|
|
373
|
+
named[name] = col.quantile(axis: 0, keep_axis: true).map do |q|
|
|
374
|
+
keep_rows ? q[CArray.int64(@nrow).fill(0), *([nil] * (q.ndim - 1))].copy : q
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
cols = CAFrame.__send__(:quantile_columns, named)
|
|
378
|
+
return CAFrame.new(cols) unless keep_rows
|
|
379
|
+
return CAFrame.new(cols, axis_name: @axis_name, index: @index)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# The five columns a quantile gives a column, by name.
|
|
383
|
+
def self.quantile_columns(named)
|
|
384
|
+
named.each_with_object({}) do |(name, quartiles), cols|
|
|
385
|
+
QUANTILE_SUFFIXES.zip(quartiles) { |suffix, q| cols["#{name}_#{suffix}"] = q }
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
private_class_method :quantile_columns
|
|
389
|
+
|
|
390
|
+
QUANTILE_SUFFIXES = %w[p0 p25 p50 p75 p100].freeze
|
|
391
|
+
private_constant :QUANTILE_SUFFIXES
|
|
392
|
+
|
|
393
|
+
private def reduce_all_rows (reduction, *names, min_count: nil, fill_value: nil, keep_rows: false, args: [])
|
|
394
|
+
if fill_value.is_a?(Hash)
|
|
395
|
+
unknown = fill_value.keys - column_names
|
|
396
|
+
unless unknown.empty?
|
|
397
|
+
raise ArgumentError,
|
|
398
|
+
"#{reduction}: fill_value names no column #{unknown.map(&:inspect).join(', ')}"
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
return reduce_down_the_rows(reduction, names, keep_rows, args) do |name|
|
|
402
|
+
fill = fill_value.is_a?(Hash) ? fill_value[name] : fill_value
|
|
403
|
+
{ min_count: min_count, fill_value: fill }.compact
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
private def reduce_all_rows_to_a_value (reduction, *names, keep_rows: false)
|
|
408
|
+
return reduce_down_the_rows(reduction, names, keep_rows) { |_| {} }
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Each named column, or every column whose type has the reduction,
|
|
412
|
+
# reduced by the column itself along the rows to one row (keep_axis), so
|
|
413
|
+
# it keeps its type as the column's reduction keeps it. The block gives a
|
|
414
|
+
# column's keywords. A frame of one row with no index; with +keep_rows+,
|
|
415
|
+
# each row holding the result, as a group's +keep_rows+ gives it.
|
|
416
|
+
private def reduce_down_the_rows (reduction, names, keep_rows, args = [])
|
|
417
|
+
meth = reduction == :count ? :count_not_masked : reduction
|
|
418
|
+
named = {}
|
|
419
|
+
out = ColumnWalk.new(self).over(reduction, names, [], args) do |col, name|
|
|
420
|
+
result = col.public_send(meth, *args, axis: 0, keep_axis: true, **yield(name))
|
|
421
|
+
if keep_rows
|
|
422
|
+
result = result[CArray.int64(@nrow).fill(0), *([nil] * (result.ndim - 1))].copy
|
|
423
|
+
end
|
|
424
|
+
named[name] = result
|
|
425
|
+
end
|
|
426
|
+
return CAFrame.new(named) unless keep_rows
|
|
427
|
+
return out if out.is_a?(CArray)
|
|
428
|
+
return CAFrame.new(named, axis_name: @axis_name, index: @index)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Values computed down the rows, one per row, as +GroupedFrame+ computes
|
|
432
|
+
# them within each group, with every row in one group: each row's rank
|
|
433
|
+
# (+order+, int64, 0 the smallest) and the running values up to and
|
|
434
|
+
# including it. Given names, one name gives the CArray and several a frame
|
|
435
|
+
# of those columns; without names, a frame of every column whose type has
|
|
436
|
+
# the operation. A frame is on the frame's rows and index.
|
|
437
|
+
#
|
|
438
|
+
# df["total"] = df.cumsum("rain")
|
|
439
|
+
# df["rank"] = df.order("temp", descending: true)
|
|
440
|
+
#
|
|
441
|
+
# @!method order(*names, descending: false, method: :ordinal, kind: :quick)
|
|
442
|
+
# @return [CAFrame, CArray]
|
|
443
|
+
# @!method cumsum(*names)
|
|
444
|
+
# @return [CAFrame, CArray]
|
|
445
|
+
# @!method cumprod(*names)
|
|
446
|
+
# @return [CAFrame, CArray]
|
|
447
|
+
# @!method cummax(*names)
|
|
448
|
+
# @return [CAFrame, CArray]
|
|
449
|
+
# @!method cummin(*names)
|
|
450
|
+
# @return [CAFrame, CArray]
|
|
451
|
+
# @!method cumcount(*names)
|
|
452
|
+
# @return [CAFrame, CArray]
|
|
453
|
+
CArray.send(:define_named_methods, self,
|
|
454
|
+
[:order, :cumsum, :cumprod, :cummax, :cummin, :cumcount], :run_down_all_rows)
|
|
455
|
+
|
|
456
|
+
private def run_down_all_rows (operation, *names, **keywords)
|
|
457
|
+
# The keywords are the core's to check, before any column is read.
|
|
458
|
+
CArray.float64(1).public_send(operation, **keywords)
|
|
459
|
+
out = ColumnWalk.new(self).over(operation, names, [], [], keywords) do |col, _|
|
|
460
|
+
col.public_send(operation, **(col.ndim > 1 ? keywords.merge(axis: 0) : keywords))
|
|
461
|
+
end
|
|
462
|
+
return out if out.is_a?(CArray)
|
|
463
|
+
return CAFrame.new(out, axis_name: @axis_name, index: @index)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# The frame's own walk over its columns: the one rule of a frame-wide verb
|
|
467
|
+
# (ColumnVerbs) -- which columns, and the shape of the answer -- for the
|
|
468
|
+
# verbs that run down all the rows, each column computed by itself.
|
|
469
|
+
class ColumnWalk
|
|
470
|
+
include CAFrame::ColumnVerbs
|
|
471
|
+
|
|
472
|
+
def initialize (frame)
|
|
473
|
+
@frame = frame
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def over (verb, names, key_names = [], args = [], keywords = {}, &block)
|
|
477
|
+
return over_named_columns(verb, names, key_names, args, keywords, &block)
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
private_constant :ColumnWalk
|
|
481
|
+
|
|
482
|
+
# The values of the columns moved along the rows: +n:+ rows back (a
|
|
483
|
+
# negative +n+ looks ahead), or the value at an exact distance of the key
|
|
484
|
+
# -- +before: "1 hour"+ gives each row the value of the row whose key is
|
|
485
|
+
# one hour earlier, +after:+ one hour later. The key is the index, or the
|
|
486
|
+
# column +on:+ names; it is a time, a duration or a number, and the
|
|
487
|
+
# distance is a duration such as "1 hour" (or a CATimedelta value) or a
|
|
488
|
+
# number accordingly. A row with no row at that key gets UNDEF; so does a
|
|
489
|
+
# row whose key is masked. The rows need not be in order. The result is
|
|
490
|
+
# new arrays in every form, so writing to it, or filling it once it is
|
|
491
|
+
# stored as a column, does not reach the column it came from.
|
|
492
|
+
#
|
|
493
|
+
# df["dtemp"] = df["temp"] - df.lag("temp", before: "1 hour")
|
|
494
|
+
# df.lag("temp", n: 1) # the previous row
|
|
495
|
+
#
|
|
496
|
+
# +GroupedFrame#lag+ does the same within each group. Given names, one
|
|
497
|
+
# name gives the CArray and several a frame of those columns; without
|
|
498
|
+
# names, a frame of every column (the +on:+ column left out). A frame is
|
|
499
|
+
# on the frame's rows and index. A result shows the column's attributes, as +CArray#lag+'s
|
|
500
|
+
# does.
|
|
501
|
+
#
|
|
502
|
+
# @param names [Array<String>] the columns.
|
|
503
|
+
# @param n [Integer] rows back.
|
|
504
|
+
# @param before [String, CATimedelta::Element, Numeric] the distance back.
|
|
505
|
+
# @param after [String, CATimedelta::Element, Numeric] the distance ahead.
|
|
506
|
+
# @param on [String] the key column, with +before:+ or +after:+ (default the
|
|
507
|
+
# index).
|
|
508
|
+
# @return [CArray, CAFrame]
|
|
509
|
+
# @raise [ArgumentError] when +n:+, +before:+ and +after:+ are combined,
|
|
510
|
+
# when +on:+ comes without a distance, when there is no key, or when the
|
|
511
|
+
# key repeats (the row at a key would not be one row).
|
|
512
|
+
def lag (*names, n: nil, before: nil, after: nil, on: nil)
|
|
513
|
+
distance = lag_distance("lag", n, before, after, on)
|
|
514
|
+
walk = ColumnWalk.new(self)
|
|
515
|
+
if distance.nil?
|
|
516
|
+
step = n.nil? ? 1 : n
|
|
517
|
+
cols = walk.over(:lag, names, [], [step]) do |col, _|
|
|
518
|
+
(col.ndim > 1 ? col.lag(step, axis: 0) : col.lag(step)).copy
|
|
519
|
+
end
|
|
520
|
+
else
|
|
521
|
+
addr = lag_addresses("lag", [CArray.int64(nrow).seq], *distance)
|
|
522
|
+
cols = walk.over(:lag, names, [distance.first], [1]) do |col, _|
|
|
523
|
+
col[addr, *([nil] * (col.ndim - 1))].copy
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
return cols if cols.is_a?(CArray)
|
|
527
|
+
CAFrame.new(cols, axis_name: @axis_name, index: @index)
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# [key name, key, sign, width] for a lag by distance; nil for one by rows.
|
|
531
|
+
private def lag_distance (verb, n, before, after, on)
|
|
532
|
+
if before.nil? && after.nil?
|
|
533
|
+
raise ArgumentError, "#{verb}: on: goes with before: or after:" unless on.nil?
|
|
534
|
+
return nil
|
|
535
|
+
end
|
|
536
|
+
unless n.nil?
|
|
537
|
+
raise ArgumentError, "#{verb}: give n: (rows) or before: / after: (a distance of the key), not both"
|
|
538
|
+
end
|
|
539
|
+
unless before.nil? || after.nil?
|
|
540
|
+
raise ArgumentError, "#{verb}: give before: or after:, not both"
|
|
541
|
+
end
|
|
542
|
+
if on.nil?
|
|
543
|
+
unless @index
|
|
544
|
+
raise ArgumentError, "#{verb}: the frame has no index; name the key column with on:"
|
|
545
|
+
end
|
|
546
|
+
on_name = @axis_name
|
|
547
|
+
else
|
|
548
|
+
on_name = column_key(on, verb)
|
|
549
|
+
end
|
|
550
|
+
key = key_column(on_name, verb)
|
|
551
|
+
unless key.is_a?(CATime) || key.is_a?(CATimedelta) || (!key.face? && key.numeric?)
|
|
552
|
+
raise ArgumentError,
|
|
553
|
+
"#{verb}: the key #{on_name.inspect} is #{CArray::Inspector.type_label(key)}; " \
|
|
554
|
+
"a distance is taken along a time, a duration or a number"
|
|
555
|
+
end
|
|
556
|
+
label = before.nil? ? "#{verb}: after:" : "#{verb}: before:"
|
|
557
|
+
width = CArray::DistanceLimit.read(before || after, key, label)
|
|
558
|
+
return [on_name, key, before.nil? ? :+ : :-, width]
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# For each row, the address of the row of its row set whose key is its own
|
|
562
|
+
# key moved by the width; masked where there is none. A row in no set is
|
|
563
|
+
# masked too, and so is a row whose key is NaN or infinite, which is no
|
|
564
|
+
# distance from any key (as in windows).
|
|
565
|
+
private def lag_addresses (verb, row_sets, _on_name, key, sign, width)
|
|
566
|
+
key = key.mask_invalid if key.float?
|
|
567
|
+
addr = CArray.int64(nrow)
|
|
568
|
+
addr[] = UNDEF
|
|
569
|
+
row_sets.each do |rows|
|
|
570
|
+
keys = key[rows]
|
|
571
|
+
next if keys.count_not_masked == 0
|
|
572
|
+
if keys.nunique < keys.count_not_masked
|
|
573
|
+
raise ArgumentError,
|
|
574
|
+
"#{verb}: the key repeats#{row_sets.size > 1 ? " within a group" : ""}, " \
|
|
575
|
+
"so the row at a key is not one row"
|
|
576
|
+
end
|
|
577
|
+
addr[rows] = rows[lag_target(keys, sign, width).locate_addr(keys)]
|
|
578
|
+
end
|
|
579
|
+
return addr
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# The keys moved by the width. A time is moved on the finer of its unit and
|
|
583
|
+
# the width's, so that an hour back from a day is not rounded onto the day.
|
|
584
|
+
private def lag_target (keys, sign, width)
|
|
585
|
+
if width.is_a?(CATimedelta::Element) &&
|
|
586
|
+
CATimeUnitAlgebra.same_group?(keys.unit.base, width.unit.base)
|
|
587
|
+
keys = keys.to_unit(CATimeUnitAlgebra.finer(keys.unit, width.unit))
|
|
588
|
+
end
|
|
589
|
+
return lag_integer_target(keys, sign, width) if keys.integer?
|
|
590
|
+
return keys.public_send(sign, width)
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
LAG_UNSIGNED = [CA_UINT8, CA_UINT16, CA_UINT32, CA_UINT64].freeze
|
|
594
|
+
private_constant :LAG_UNSIGNED
|
|
595
|
+
|
|
596
|
+
# Integer keys moved by the width exactly, in int64 (or as Ruby integers
|
|
597
|
+
# for a 64-bit key a step does not fit in). A target outside the key's
|
|
598
|
+
# range is masked, so it matches no row rather than wrapping onto one; a
|
|
599
|
+
# width that is not a whole number matches no row either.
|
|
600
|
+
private def lag_integer_target (keys, sign, width)
|
|
601
|
+
bits = keys.bytes * 8
|
|
602
|
+
if LAG_UNSIGNED.include?(keys.data_type)
|
|
603
|
+
lo, hi = 0, 2**bits - 1
|
|
604
|
+
else
|
|
605
|
+
lo, hi = -2**(bits - 1), 2**(bits - 1) - 1
|
|
606
|
+
end
|
|
607
|
+
step = width.is_a?(Integer) ? width : nil
|
|
608
|
+
if step.nil? && width.respond_to?(:finite?) && width.finite? && width == width.floor
|
|
609
|
+
step = width.to_i
|
|
610
|
+
end
|
|
611
|
+
if step.nil? || step > hi - lo
|
|
612
|
+
out = CArray.int64(*keys.shape)
|
|
613
|
+
out[] = UNDEF
|
|
614
|
+
return out
|
|
615
|
+
end
|
|
616
|
+
if bits < 64
|
|
617
|
+
return keys.to_type(CA_INT64).public_send(sign, step)
|
|
618
|
+
end
|
|
619
|
+
if step > 2**63 - 1
|
|
620
|
+
moved = keys.to_type(CA_OBJECT).public_send(sign, step)
|
|
621
|
+
moved[moved.lt(lo).or(moved.gt(hi))] = UNDEF
|
|
622
|
+
return moved
|
|
623
|
+
end
|
|
624
|
+
out = keys.copy
|
|
625
|
+
out[sign == :- ? keys.lt(lo + step) : keys.gt(hi - step)] = UNDEF
|
|
626
|
+
return out.public_send(sign, step)
|
|
627
|
+
end
|
|
628
|
+
|
|
237
629
|
end
|
|
238
630
|
|
|
239
631
|
# GroupedFrame — the result of +CAFrame#group_by+ (memo §11.6, §16).
|
|
240
632
|
#
|
|
241
633
|
# Holds the grouping categorical once (grouping is per-key, computed once
|
|
242
|
-
# and shared across every column).
|
|
243
|
-
# grp["col"]
|
|
244
|
-
# aggregate
|
|
245
|
-
#
|
|
634
|
+
# and shared across every column). The surfaces:
|
|
635
|
+
# grp["col"], grp[array] -> the CArray group iterator of a column or array
|
|
636
|
+
# aggregate(spec) -> declarative per-column reductions into a new frame
|
|
637
|
+
# aggregate { |grp| } -> columns built from iterators, run once
|
|
638
|
+
# table { |g| } -> Ruby escape, g is a per-group view-frame
|
|
246
639
|
class GroupedFrame
|
|
247
640
|
include CAFrame::ColumnVerbs
|
|
248
641
|
# +key_names+ are the frame columns the grouping was keyed on. They become
|
|
@@ -280,15 +673,79 @@ class GroupedFrame
|
|
|
280
673
|
@labels.size
|
|
281
674
|
end
|
|
282
675
|
|
|
676
|
+
# The number of rows in each group, as a frame of one column, "size"
|
|
677
|
+
# unless +name:+ says otherwise, labelled as a reduction's result is. A row
|
|
678
|
+
# whose key is masked is in no group, so the sizes need not add up to the
|
|
679
|
+
# frame's nrow.
|
|
680
|
+
#
|
|
681
|
+
# @param name [String] the result column's name.
|
|
682
|
+
# @return [CAFrame]
|
|
683
|
+
# @raise [ArgumentError] when a key has that name.
|
|
684
|
+
def size (name: "size")
|
|
685
|
+
refuse_changed_rows("size")
|
|
686
|
+
name = @frame.__send__(:column_name_arg, name, "size")
|
|
687
|
+
if @key_names.include?(name) || @axis_name == name
|
|
688
|
+
raise ArgumentError,
|
|
689
|
+
"size: a key is named #{name.inspect}; name the result otherwise, as in size(name: \"n\")"
|
|
690
|
+
end
|
|
691
|
+
return result_frame({ name => @cat.category_sizes.int64 }, false)
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
# Each group's label (an Array of the key values for several keys) and its
|
|
695
|
+
# rows as a sub-frame sharing the frame's columns, in the order of
|
|
696
|
+
# +labels+. Without a block, an Enumerator.
|
|
697
|
+
#
|
|
698
|
+
# df.group_by("station").each { |station, rows| puts station, rows.nrow }
|
|
699
|
+
#
|
|
700
|
+
# @yieldparam label [Object, Array] the group's key value(s).
|
|
701
|
+
# @yieldparam rows [CAFrame] the group's rows.
|
|
702
|
+
# @return [GroupedFrame] self
|
|
703
|
+
def each
|
|
704
|
+
return enum_for(:each) { ngroup } unless block_given?
|
|
705
|
+
refuse_changed_rows("each")
|
|
706
|
+
ngroup.times { |k| yield @labels[k], @frame[group_address(k)] }
|
|
707
|
+
return self
|
|
708
|
+
end
|
|
709
|
+
|
|
283
710
|
# Group key values (one per group), as an Array.
|
|
284
711
|
def labels
|
|
285
712
|
@labels.dup
|
|
286
713
|
end
|
|
287
714
|
|
|
288
|
-
#
|
|
289
|
-
|
|
715
|
+
# The CArray group iterator of a column, or of any CArray with one entry
|
|
716
|
+
# per row of the frame: a value computed from columns, or a column with
|
|
717
|
+
# some cells masked to leave them out. Its reductions answer one value
|
|
718
|
+
# per group, and take the arguments a CArray's reductions take.
|
|
719
|
+
#
|
|
720
|
+
# grp["temp"].percentile(90)
|
|
721
|
+
# grp[df["temp"] * df["w"]].sum / grp["w"].sum
|
|
722
|
+
# grp[df["temp"].mask_where(df["qc"].ne(0))].mean # without the flagged rows
|
|
723
|
+
#
|
|
724
|
+
# The array is matched to the rows by position, as a CArray key of
|
|
725
|
+
# +group_by+ is. An N-D one is grouped along its first axis; pass
|
|
726
|
+
# +axis: 0+ to its reductions to keep the trailing shape.
|
|
727
|
+
#
|
|
728
|
+
# @param source [String, CArray] a column name, or a CArray whose first
|
|
729
|
+
# axis has one entry per row.
|
|
730
|
+
# @return [CACategoricalIterator]
|
|
731
|
+
# @raise [KeyError] for a name that is no column.
|
|
732
|
+
# @raise [ArgumentError] when the CArray's first axis is not the rows.
|
|
733
|
+
def [](source)
|
|
290
734
|
refuse_changed_rows("[]")
|
|
291
|
-
|
|
735
|
+
values =
|
|
736
|
+
case source
|
|
737
|
+
when CArray
|
|
738
|
+
rows = source.ndim == 0 ? 0 : source.shape[0]
|
|
739
|
+
unless source.ndim >= 1 && rows == @cat.elements
|
|
740
|
+
raise ArgumentError,
|
|
741
|
+
"[]: the array has #{source.ndim == 0 ? "no axis" : "#{rows} rows"} " \
|
|
742
|
+
"for the frame's #{@cat.elements}"
|
|
743
|
+
end
|
|
744
|
+
source
|
|
745
|
+
else
|
|
746
|
+
@frame[@frame.__send__(:column_key, source, "[]")]
|
|
747
|
+
end
|
|
748
|
+
values.group_by_category(@cat)
|
|
292
749
|
end
|
|
293
750
|
|
|
294
751
|
# Declarative aggregation (memo §11.6). Spec maps an output column name to
|
|
@@ -304,12 +761,44 @@ class GroupedFrame
|
|
|
304
761
|
# keyword works for +table+ and the reductions below.
|
|
305
762
|
#
|
|
306
763
|
# df.group_by("station").aggregate("temp_mean" => ["temp", :mean], keep_rows: true)
|
|
307
|
-
|
|
764
|
+
#
|
|
765
|
+
# Given a block instead of a spec, aggregate yields the grouped frame once
|
|
766
|
+
# and the block returns a Hash of output name => a CArray with one entry
|
|
767
|
+
# per group. The block computes the columns with the grouped frame's
|
|
768
|
+
# reductions, +grp[...]+ iterators and CArray arithmetic, so nothing runs
|
|
769
|
+
# per group in Ruby:
|
|
770
|
+
#
|
|
771
|
+
# df.group_by("station").aggregate do |g|
|
|
772
|
+
# {
|
|
773
|
+
# "temp" => g["temp"].mean,
|
|
774
|
+
# "range" => g["temp"].max - g["temp"].min,
|
|
775
|
+
# "wmean" => g[df["temp"] * df["w"]].sum / g["w"].sum,
|
|
776
|
+
# "n" => g["temp"].group_sizes,
|
|
777
|
+
# }
|
|
778
|
+
# end
|
|
779
|
+
#
|
|
780
|
+
# +keep_rows: true+ lays each column over the rows as for a spec.
|
|
781
|
+
#
|
|
782
|
+
# @raise [ArgumentError] when both a spec and a block are given, or the
|
|
783
|
+
# block returns something other than a Hash of per-group CArrays.
|
|
784
|
+
def aggregate(spec = nil, keep_rows: false, **named, &block)
|
|
308
785
|
refuse_changed_rows("aggregate")
|
|
786
|
+
if block
|
|
787
|
+
unless spec.nil? && named.empty?
|
|
788
|
+
raise ArgumentError, "aggregate: give a spec or a block, not both"
|
|
789
|
+
end
|
|
790
|
+
return result_frame(block_columns(yield(self)), keep_rows)
|
|
791
|
+
end
|
|
309
792
|
spec = (spec || {}).merge(named)
|
|
310
793
|
cols = {}
|
|
311
794
|
spec.each do |out_name, (in_name, reduction, *args)|
|
|
312
795
|
out = @frame.__send__(:column_name_arg, out_name, "aggregate")
|
|
796
|
+
if in_name.is_a?(Symbol)
|
|
797
|
+
raise ArgumentError,
|
|
798
|
+
"aggregate: each entry is result name => [column, reduction] " \
|
|
799
|
+
"(got #{out_name.inspect} => #{spec[out_name].inspect}); " \
|
|
800
|
+
"as in aggregate(\"mean_#{out_name}\" => [#{out_name.inspect}, :mean])"
|
|
801
|
+
end
|
|
313
802
|
@frame.__send__(:column_key, in_name, "aggregate")
|
|
314
803
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
315
804
|
cols[out] =
|
|
@@ -364,23 +853,29 @@ class GroupedFrame
|
|
|
364
853
|
# its trailing shape: a (N, L) column gives (groups, L). Each
|
|
365
854
|
# takes +min_count:+ and +fill_value:+ as a core reduction does: a group
|
|
366
855
|
# with fewer than min_count present values is UNDEF, and fill_value fills
|
|
367
|
-
# the UNDEF cells.
|
|
856
|
+
# the UNDEF cells. One fill_value goes to every column and has to suit each
|
|
857
|
+
# (a number into a string column raises); a Hash of column name => value
|
|
858
|
+
# fills those columns only. +keep_rows: true+ gives each row its group's value
|
|
368
859
|
# instead of one row per group, as for +aggregate+.
|
|
369
860
|
#
|
|
370
|
-
# Given column names, they reduce those columns only and
|
|
371
|
-
#
|
|
861
|
+
# Given column names, they reduce those columns only, and the result is
|
|
862
|
+
# the frame of those columns, one row per group, labelled as above; with
|
|
863
|
+
# +keep_rows: true+ one comes back as +df[name]+ returns it, a CArray on
|
|
864
|
+
# the frame's rows, and several as a frame on those rows. For a group's
|
|
865
|
+
# values alone, reduce the column's group iterator: +grp["temp"].mean+.
|
|
372
866
|
#
|
|
373
867
|
# grp.mean # a frame, one row per group
|
|
374
|
-
# grp.mean("temp") #
|
|
868
|
+
# grp.mean("temp") # a frame of temp, one row per group
|
|
375
869
|
# df["temp"] - grp.mean("temp", keep_rows: true) # anomaly from the group mean
|
|
376
870
|
#
|
|
377
871
|
# @!method sum(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
378
872
|
# Returns a frame of the per-group sum of every column whose type has a
|
|
379
873
|
# sum, an N-D one per group along the rows. Other columns are left out.
|
|
380
|
-
# @return [CAFrame, CArray
|
|
381
|
-
# group, indexed by the group labels
|
|
382
|
-
# frame's rows and index; given
|
|
383
|
-
#
|
|
874
|
+
# @return [CAFrame, CArray] a frame with one row per
|
|
875
|
+
# group, indexed by the group labels (of the named columns when names
|
|
876
|
+
# are given), or with keep_rows: true the frame's rows and index; given
|
|
877
|
+
# names and keep_rows: true, the CArray (one) or a frame of them on the
|
|
878
|
+
# frame's rows (several).
|
|
384
879
|
# @!method mean(*names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
385
880
|
# Returns a frame of the per-group mean of every column whose type has
|
|
386
881
|
# a mean, as {#sum} does.
|
|
@@ -424,6 +919,43 @@ class GroupedFrame
|
|
|
424
919
|
reduce_columns(red, names, keep_rows, min_count: min_count, fill_value: fill_value)
|
|
425
920
|
end
|
|
426
921
|
|
|
922
|
+
# Each group's +p+-th percentile (0 to 100), as {#median} gives the 50th
|
|
923
|
+
# and {#sum} gives its sums. The percentage comes first, then the names.
|
|
924
|
+
#
|
|
925
|
+
# grp.percentile(90, "revenue")
|
|
926
|
+
#
|
|
927
|
+
# @param p [Numeric] the percentage.
|
|
928
|
+
# @param names [Array<String>] the columns; default every column that has
|
|
929
|
+
# a percentile.
|
|
930
|
+
# @return [CAFrame, CArray] one row per group, as {#sum} returns.
|
|
931
|
+
def percentile (p, *names, min_count: nil, fill_value: nil, keep_rows: false)
|
|
932
|
+
CAFrame.__send__(:check_percentage, p)
|
|
933
|
+
refuse_changed_rows(:percentile)
|
|
934
|
+
reduce_columns(:percentile, names, keep_rows, args: [p], min_count: min_count, fill_value: fill_value)
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
# Each group's five quartile percentiles, as +CAFrame#quantile+ gives them
|
|
938
|
+
# for all the rows: a column "x" gives "x_p0", "x_p25", "x_p50", "x_p75"
|
|
939
|
+
# and "x_p100", one row per group, labelled as {#sum} labels its result;
|
|
940
|
+
# with +keep_rows: true+ on the frame's rows.
|
|
941
|
+
#
|
|
942
|
+
# grp.quantile("temp")
|
|
943
|
+
#
|
|
944
|
+
# @param names [Array<String>] the columns; default every column that has
|
|
945
|
+
# a quantile.
|
|
946
|
+
# @return [CAFrame]
|
|
947
|
+
def quantile (*names, keep_rows: false)
|
|
948
|
+
refuse_changed_rows(:quantile)
|
|
949
|
+
refuse_key_as_result(:quantile, names) unless keep_rows
|
|
950
|
+
named = {}
|
|
951
|
+
over_named_columns(:quantile, names, @key_names) do |col, name|
|
|
952
|
+
named[name] = group_reduce(col, :quantile).map { |q| keep_rows ? spread_to_rows(q) : q }
|
|
953
|
+
end
|
|
954
|
+
cols = CAFrame.__send__(:quantile_columns, named)
|
|
955
|
+
return result_frame(cols, false) unless keep_rows
|
|
956
|
+
return CAFrame.new(cols, axis_name: @frame.axis_name, index: @frame.index)
|
|
957
|
+
end
|
|
958
|
+
|
|
427
959
|
# The same convenience for the reductions that take no min_count: or
|
|
428
960
|
# fill_value:, as the core ones take none: a value of the group (first,
|
|
429
961
|
# last), a count (nunique, count) or a boolean fold (all, any).
|
|
@@ -463,8 +995,8 @@ class GroupedFrame
|
|
|
463
995
|
# group's rows, a running statistic. A row in no group is UNDEF. Without
|
|
464
996
|
# names they give a frame of every column whose type has the operation
|
|
465
997
|
# (the key columns left out), on the frame's rows and index; given names,
|
|
466
|
-
# the CArray (one)
|
|
467
|
-
# columns. An N-D column runs along the rows.
|
|
998
|
+
# the CArray (one), as +df[name]+ returns a column, or a frame of those
|
|
999
|
+
# columns (several) on the frame's rows and index. An N-D column runs along the rows.
|
|
468
1000
|
#
|
|
469
1001
|
# df["dtemp"] = df["temp"] - grp.lag("temp") # change since the station's previous row
|
|
470
1002
|
# grp.order("temp", descending: true) # rank within the station
|
|
@@ -473,31 +1005,43 @@ class GroupedFrame
|
|
|
473
1005
|
# "Previous" is the previous row of the frame: sort the frame first
|
|
474
1006
|
# (+sort_by_key("time")+) when its rows are not in time order.
|
|
475
1007
|
#
|
|
476
|
-
# @!method lag(*names, n: 1)
|
|
1008
|
+
# @!method lag(*names, n: 1, before: nil, after: nil, on: nil)
|
|
477
1009
|
# Returns each row's value +n+ rows back in its own group, UNDEF where
|
|
478
|
-
# the group has none, as +CArray#lag+; a negative +n+ looks ahead.
|
|
479
|
-
#
|
|
1010
|
+
# the group has none, as +CArray#lag+; a negative +n+ looks ahead. With
|
|
1011
|
+
# +before:+ (or +after:+) instead, the value of the group's row whose key
|
|
1012
|
+
# (the index, or the column +on:+ names) is that distance earlier (or
|
|
1013
|
+
# later), as +CAFrame#lag+ gives it.
|
|
1014
|
+
# @return [CAFrame, CArray]
|
|
480
1015
|
# @!method order(*names, descending: false, method: :ordinal, kind: :quick)
|
|
481
1016
|
# Returns each row's rank among its group's rows (int64, 0 the
|
|
482
1017
|
# smallest), as +CArray#order+ ranks them.
|
|
483
|
-
# @return [CAFrame, CArray
|
|
1018
|
+
# @return [CAFrame, CArray]
|
|
484
1019
|
# @!method cumsum(*names)
|
|
485
1020
|
# Returns the running sum within each group, as +CArray#cumsum+.
|
|
486
|
-
# @return [CAFrame, CArray
|
|
1021
|
+
# @return [CAFrame, CArray]
|
|
487
1022
|
# @!method cumprod(*names)
|
|
488
1023
|
# Returns the running product within each group.
|
|
489
|
-
# @return [CAFrame, CArray
|
|
1024
|
+
# @return [CAFrame, CArray]
|
|
490
1025
|
# @!method cummax(*names)
|
|
491
1026
|
# Returns the running maximum within each group.
|
|
492
|
-
# @return [CAFrame, CArray
|
|
1027
|
+
# @return [CAFrame, CArray]
|
|
493
1028
|
# @!method cummin(*names)
|
|
494
1029
|
# Returns the running minimum within each group.
|
|
495
|
-
# @return [CAFrame, CArray
|
|
1030
|
+
# @return [CAFrame, CArray]
|
|
496
1031
|
# @!method cumcount(*names)
|
|
497
1032
|
# Returns the running count of present values within each group (int64).
|
|
498
|
-
# @return [CAFrame, CArray
|
|
499
|
-
def lag (*names, n:
|
|
500
|
-
|
|
1033
|
+
# @return [CAFrame, CArray]
|
|
1034
|
+
def lag (*names, n: nil, before: nil, after: nil, on: nil)
|
|
1035
|
+
distance = @frame.__send__(:lag_distance, "lag", n, before, after, on)
|
|
1036
|
+
return transform_columns(:lag, names, [n.nil? ? 1 : n], {}) if distance.nil?
|
|
1037
|
+
refuse_changed_rows("lag")
|
|
1038
|
+
refuse_key_on_rows(:lag, names)
|
|
1039
|
+
addr = @frame.__send__(:lag_addresses, "lag", (0...ngroup).map { |k| group_address(k) }, *distance)
|
|
1040
|
+
out = over_named_columns(:lag, names, @key_names | [distance.first], [1], {}) do |col|
|
|
1041
|
+
col[addr, *([nil] * (col.ndim - 1))].copy
|
|
1042
|
+
end
|
|
1043
|
+
return out if out.is_a?(CArray)
|
|
1044
|
+
CAFrame.new(out, axis_name: @frame.axis_name, index: @frame.index)
|
|
501
1045
|
end
|
|
502
1046
|
|
|
503
1047
|
def order (*names, descending: false, method: :ordinal, kind: :quick)
|
|
@@ -514,22 +1058,47 @@ class GroupedFrame
|
|
|
514
1058
|
# Fill the masked cells of a column from the rows of the same group, in
|
|
515
1059
|
# place, as +CAFrame#fill+ does for the whole column: +:ffill+ carries the
|
|
516
1060
|
# group's last present value forward in row order, +:bfill+ its next one
|
|
517
|
-
# back
|
|
518
|
-
#
|
|
519
|
-
#
|
|
520
|
-
#
|
|
1061
|
+
# back, and +:linear+ interpolates between the group's present values
|
|
1062
|
+
# against the frame's index (against the row position within the group
|
|
1063
|
+
# when the frame has no index). A fill never reaches across into another
|
|
1064
|
+
# group, so a cell with nothing to carry or interpolate from in its group
|
|
1065
|
+
# stays masked, and a row whose key is masked (in no group) is left as it
|
|
1066
|
+
# is. A constant fills every masked cell, as +CAFrame#fill+ does. Returns
|
|
1067
|
+
# self.
|
|
521
1068
|
#
|
|
522
1069
|
# df.group_by("station").fill("temp", :ffill)
|
|
1070
|
+
# df.group_by("station").fill("temp", :linear)
|
|
1071
|
+
# df.group_by("station").fill("temp" => :linear, "rain" => 0.0)
|
|
1072
|
+
# df.group_by("station").fill("temp", "rain", :linear)
|
|
1073
|
+
# df.group_by("station").fill(:ffill) # every column with a masked cell
|
|
523
1074
|
#
|
|
524
|
-
#
|
|
525
|
-
# time order.
|
|
1075
|
+
# +:ffill+ and +:bfill+ follow the row order: sort the frame first
|
|
1076
|
+
# (+sort_by_key("time")+) when its rows are not in time order. +:linear+
|
|
1077
|
+
# reads each group in the order of the index, as +CAFrame#fill+ does.
|
|
526
1078
|
#
|
|
527
|
-
# @param
|
|
528
|
-
#
|
|
1079
|
+
# @param args [Array] the columns, as +CAFrame#fill+ takes them, then
|
|
1080
|
+
# +:ffill+, +:bfill+, +:linear+, or a constant; or one Hash of them.
|
|
529
1081
|
# @return [GroupedFrame] self
|
|
530
|
-
# @raise [ArgumentError] for +:linear
|
|
531
|
-
#
|
|
532
|
-
def fill (
|
|
1082
|
+
# @raise [ArgumentError] for another Symbol, or for +:linear+ on a column
|
|
1083
|
+
# or index +CAFrame#fill+ cannot interpolate.
|
|
1084
|
+
def fill (*args)
|
|
1085
|
+
plan = @frame.__send__(:fill_arguments, args, @key_names)
|
|
1086
|
+
return self if plan.nil?
|
|
1087
|
+
refuse_key_on_rows(:fill, plan.is_a?(Hash) ? plan.keys : [plan.first])
|
|
1088
|
+
if plan.is_a?(Hash)
|
|
1089
|
+
@frame.__send__(:fill_by_map, plan, "fill") do |frame, key, how|
|
|
1090
|
+
grouped = if frame.equal?(@frame)
|
|
1091
|
+
self
|
|
1092
|
+
else
|
|
1093
|
+
# key-labels: the groups' index as it is, on a copy of the frame
|
|
1094
|
+
GroupedFrame.new(frame, @cat, @axis_name, @key_names, index: @index,
|
|
1095
|
+
label_columns: @label_columns)
|
|
1096
|
+
end
|
|
1097
|
+
grouped.fill(key, how)
|
|
1098
|
+
end
|
|
1099
|
+
return self
|
|
1100
|
+
end
|
|
1101
|
+
name, method_or_value = plan
|
|
533
1102
|
refuse_changed_rows("fill")
|
|
534
1103
|
case method_or_value
|
|
535
1104
|
when :ffill, :bfill
|
|
@@ -541,10 +1110,15 @@ class GroupedFrame
|
|
|
541
1110
|
target = col.is_masked
|
|
542
1111
|
col[target] = held[target] if target.any
|
|
543
1112
|
when :linear
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1113
|
+
@frame.__send__(:refuse_if_frozen)
|
|
1114
|
+
key = @frame.__send__(:column_key, name, "fill")
|
|
1115
|
+
# The column and the index are checked as CAFrame#fill checks them,
|
|
1116
|
+
# on no rows, so a call without groups is refused as one with groups.
|
|
1117
|
+
@frame.__send__(:select_rows, CA_INT64([])).fill(key, :linear)
|
|
1118
|
+
# Each group's rows as a frame of views, so its fill writes through.
|
|
1119
|
+
ngroup.times do |k|
|
|
1120
|
+
@frame.__send__(:select_rows, group_address(k)).fill(key, :linear)
|
|
1121
|
+
end
|
|
548
1122
|
when Symbol
|
|
549
1123
|
raise ArgumentError,
|
|
550
1124
|
"fill: unknown method #{method_or_value.inspect} " \
|
|
@@ -555,32 +1129,113 @@ class GroupedFrame
|
|
|
555
1129
|
self
|
|
556
1130
|
end
|
|
557
1131
|
|
|
1132
|
+
# The first +n+ rows of each group, in row order, as a view-frame: the
|
|
1133
|
+
# rows stay in the frame's order, as +filter+ keeps them. A group with
|
|
1134
|
+
# fewer than +n+ rows gives all of them; a row whose group key is masked
|
|
1135
|
+
# is in no group.
|
|
1136
|
+
#
|
|
1137
|
+
# df.group_by("station").head(1) # each station's first row
|
|
1138
|
+
#
|
|
1139
|
+
# @param n [Integer] how many rows per group, 0 or more (default 5).
|
|
1140
|
+
# @return [CAFrame]
|
|
1141
|
+
def head (n = 5)
|
|
1142
|
+
return rows_at_the_ends_of_groups("head", n) { |addr| addr.first(n) }
|
|
1143
|
+
end
|
|
1144
|
+
|
|
1145
|
+
# The last +n+ rows of each group, in row order, as +head+ gives the first.
|
|
1146
|
+
#
|
|
1147
|
+
# @param n [Integer] how many rows per group, 0 or more (default 5).
|
|
1148
|
+
# @return [CAFrame]
|
|
1149
|
+
def tail (n = 5)
|
|
1150
|
+
return rows_at_the_ends_of_groups("tail", n) { |addr| addr.last(n) }
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1153
|
+
private def rows_at_the_ends_of_groups (verb, n)
|
|
1154
|
+
refuse_changed_rows(verb)
|
|
1155
|
+
unless n.is_a?(Integer)
|
|
1156
|
+
raise TypeError, "#{verb}: the count is an Integer (got #{n.inspect})"
|
|
1157
|
+
end
|
|
1158
|
+
raise ArgumentError, "#{verb}: the count must be 0 or more (got #{n})" if n < 0
|
|
1159
|
+
picked = (0...ngroup).flat_map { |k| yield(group_address(k).to_a) }.sort
|
|
1160
|
+
return @frame[CA_INT64(picked)]
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
# The +n+ rows of each group with the largest values of the key columns,
|
|
1164
|
+
# as a view-frame: the groups in the order of +labels+, each group's rows
|
|
1165
|
+
# largest first, as +CAFrame#nlargest+ orders them (several keys break
|
|
1166
|
+
# ties in turn, a NaN or masked key loses to every value, rows that tie on
|
|
1167
|
+
# every key keep their order). A group with fewer than +n+ rows gives all
|
|
1168
|
+
# of them; a row whose group key is masked is in no group.
|
|
1169
|
+
#
|
|
1170
|
+
# df.group_by("station").nlargest(3, "temp") # each station's 3 warmest rows
|
|
1171
|
+
#
|
|
1172
|
+
# @param n [Integer] how many rows per group, 0 or more.
|
|
1173
|
+
# @param keys [Array<String>] the key columns (or the index axis name).
|
|
1174
|
+
# @return [CAFrame]
|
|
1175
|
+
# @raise [ArgumentError] when +n+ is negative or no key is given.
|
|
1176
|
+
def nlargest (n, *keys)
|
|
1177
|
+
return largest_rows_of_groups("nlargest", n, keys, true)
|
|
1178
|
+
end
|
|
1179
|
+
|
|
1180
|
+
# The +n+ rows of each group with the smallest values of the key columns,
|
|
1181
|
+
# as +nlargest+ gives the largest.
|
|
1182
|
+
#
|
|
1183
|
+
# @param n [Integer] how many rows per group, 0 or more.
|
|
1184
|
+
# @param keys [Array<String>] the key columns (or the index axis name).
|
|
1185
|
+
# @return [CAFrame]
|
|
1186
|
+
# @raise [ArgumentError] when +n+ is negative or no key is given.
|
|
1187
|
+
def nsmallest (n, *keys)
|
|
1188
|
+
return largest_rows_of_groups("nsmallest", n, keys, false)
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
private def largest_rows_of_groups (verb, n, keys, descending)
|
|
1192
|
+
refuse_changed_rows(verb)
|
|
1193
|
+
unless n.is_a?(Integer)
|
|
1194
|
+
raise TypeError, "#{verb}: the count is an Integer (got #{n.inspect})"
|
|
1195
|
+
end
|
|
1196
|
+
raise ArgumentError, "#{verb}: the count must be 0 or more (got #{n})" if n < 0
|
|
1197
|
+
raise ArgumentError, "#{verb}: give at least one key column" if keys.empty?
|
|
1198
|
+
cols = keys.map do |key|
|
|
1199
|
+
@frame.__send__(:key_column, @frame.__send__(:column_key, key, verb), verb)
|
|
1200
|
+
end
|
|
1201
|
+
sort_keys = cols.map { |col| descending ? col.order(descending: true, method: :dense) : col }
|
|
1202
|
+
# Rows sorted by group, then by the keys; a row in no group comes last.
|
|
1203
|
+
addr = CArray.sort_addr(group_codes, *sort_keys, masked_position: :last).to_a
|
|
1204
|
+
picked = []
|
|
1205
|
+
start = 0
|
|
1206
|
+
@cat.category_sizes.to_a.each do |size|
|
|
1207
|
+
picked.concat(addr[start, [n, size].min]) if n > 0
|
|
1208
|
+
start += size
|
|
1209
|
+
end
|
|
1210
|
+
return @frame[CA_INT64(picked)]
|
|
1211
|
+
end
|
|
1212
|
+
|
|
558
1213
|
# Keep the rows of the groups the block chooses. The block receives the
|
|
559
1214
|
# grouped frame and returns a boolean CArray with one value per group,
|
|
560
1215
|
# built from the group reductions, as +CAFrame#filter+'s block returns one
|
|
561
1216
|
# per row:
|
|
562
1217
|
#
|
|
563
1218
|
# grp.filter { |g| g["temp"].count_not_masked >= 20 } # stations with 20 readings
|
|
564
|
-
# grp.filter { |g| g
|
|
1219
|
+
# grp.filter { |g| g["temp"].mean > 15.0 }
|
|
565
1220
|
#
|
|
566
1221
|
# The rows of a chosen group are kept in the frame's order, as a sub-frame
|
|
567
1222
|
# sharing the frame's columns. A group whose value is UNDEF is
|
|
568
1223
|
# undetermined, as is a row whose key is masked (it belongs to no group):
|
|
569
|
-
# such rows are
|
|
570
|
-
# cells masked, as +CAFrame#filter+ does.
|
|
1224
|
+
# such rows are not kept, as +CAFrame#filter+ does not keep them.
|
|
571
1225
|
#
|
|
572
|
-
# @param keep_masked [Boolean] carry the undetermined rows forward masked.
|
|
573
1226
|
# @yieldparam grouped [GroupedFrame] self.
|
|
574
1227
|
# @yieldreturn [CArray] boolean, one value per group.
|
|
575
1228
|
# @return [CAFrame]
|
|
576
1229
|
# @raise [ArgumentError] when the block does not return a boolean CArray of
|
|
577
1230
|
# shape [ngroup].
|
|
578
|
-
def filter (
|
|
1231
|
+
def filter (**options)
|
|
1232
|
+
CAFrame.__send__(:refuse_filter_keywords, options, "filter")
|
|
579
1233
|
refuse_changed_rows("filter")
|
|
580
1234
|
chosen = yield(self)
|
|
581
1235
|
unless chosen.is_a?(CArray) && chosen.data_type == :boolean
|
|
582
1236
|
raise ArgumentError,
|
|
583
|
-
"filter block must return a boolean CArray (got #{chosen.is_a?(CArray) ? "a #{CArray::Inspector.type_label(chosen)} CArray" : chosen.class})"
|
|
1237
|
+
"filter block must return a boolean CArray (got #{chosen.is_a?(CArray) ? "a #{CArray::Inspector.type_label(chosen)} CArray" : chosen.class})" +
|
|
1238
|
+
CAFrame.__send__(:equality_hint, chosen)
|
|
584
1239
|
end
|
|
585
1240
|
unless chosen.shape == [ngroup]
|
|
586
1241
|
raise ArgumentError,
|
|
@@ -588,7 +1243,7 @@ class GroupedFrame
|
|
|
588
1243
|
"expected [#{ngroup}] (one value per group)"
|
|
589
1244
|
end
|
|
590
1245
|
rows = spread_to_rows(chosen)
|
|
591
|
-
@frame.filter(
|
|
1246
|
+
@frame.filter(rows)
|
|
592
1247
|
end
|
|
593
1248
|
|
|
594
1249
|
# Returns the grouped rows as a frame: the frame the grouping was made
|
|
@@ -597,7 +1252,7 @@ class GroupedFrame
|
|
|
597
1252
|
# rows; adding or dropping a column changes the new frame only.
|
|
598
1253
|
#
|
|
599
1254
|
# def warm_stations (grp)
|
|
600
|
-
# grp.filter { |g| g
|
|
1255
|
+
# grp.filter { |g| g["temp"].mean > 15.0 } # the rows of some groups
|
|
601
1256
|
# end
|
|
602
1257
|
# grp.to_frame # all of its rows
|
|
603
1258
|
#
|
|
@@ -618,11 +1273,12 @@ class GroupedFrame
|
|
|
618
1273
|
# would otherwise leave every column out without a word.
|
|
619
1274
|
private def transform_columns(op, names, args, keywords)
|
|
620
1275
|
refuse_changed_rows(op)
|
|
1276
|
+
refuse_key_on_rows(op, names)
|
|
621
1277
|
CArray.float64(1).public_send(op, *args, **keywords)
|
|
622
1278
|
out = over_named_columns(op, names, @key_names, args, keywords) do |col|
|
|
623
1279
|
group_transform(col, op, args, keywords)
|
|
624
1280
|
end
|
|
625
|
-
return out
|
|
1281
|
+
return out if out.is_a?(CArray)
|
|
626
1282
|
CAFrame.new(out, axis_name: @frame.axis_name, index: @frame.index)
|
|
627
1283
|
end
|
|
628
1284
|
|
|
@@ -639,17 +1295,56 @@ class GroupedFrame
|
|
|
639
1295
|
CArray.object(@labels.size) { |i| @labels[i] }
|
|
640
1296
|
end
|
|
641
1297
|
|
|
642
|
-
# A key column is the index here, not a result column. Named columns
|
|
643
|
-
#
|
|
644
|
-
#
|
|
645
|
-
private def reduce_columns(reduction, names, keep_rows, **keywords)
|
|
1298
|
+
# A key column is the index here, not a result column. Named columns give
|
|
1299
|
+
# the frame of those columns only, one row per group; with +keep_rows+
|
|
1300
|
+
# they come back as df[...] returns them, each on the frame's rows.
|
|
1301
|
+
private def reduce_columns(reduction, names, keep_rows, args: [], **keywords)
|
|
1302
|
+
refuse_key_as_result(reduction, names) unless keep_rows
|
|
646
1303
|
keywords = keywords.compact
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
1304
|
+
fills = keywords.delete(:fill_value)
|
|
1305
|
+
if fills.is_a?(Hash)
|
|
1306
|
+
unknown = fills.keys - @frame.column_names
|
|
1307
|
+
unless unknown.empty?
|
|
1308
|
+
raise ArgumentError,
|
|
1309
|
+
"#{reduction}: fill_value names no column #{unknown.map(&:inspect).join(', ')}"
|
|
1310
|
+
end
|
|
1311
|
+
end
|
|
1312
|
+
named = {}
|
|
1313
|
+
out = over_named_columns(reduction, names, @key_names, args) do |col, name|
|
|
1314
|
+
fill = fills.is_a?(Hash) ? fills[name] : fills
|
|
1315
|
+
kw = fill.nil? ? keywords : keywords.merge(fill_value: fill)
|
|
1316
|
+
col = group_reduce(col, reduction, *args, **kw)
|
|
1317
|
+
named[name] = keep_rows && !names.empty? ? spread_to_rows(col) : col
|
|
650
1318
|
end
|
|
651
|
-
return out
|
|
652
|
-
|
|
1319
|
+
return result_frame(out, keep_rows) if names.empty?
|
|
1320
|
+
# One named column on the frame's rows comes back as df[name] returns it,
|
|
1321
|
+
# several as a frame on those rows; one value per group needs the group
|
|
1322
|
+
# labels beside it.
|
|
1323
|
+
if keep_rows
|
|
1324
|
+
return out if out.is_a?(CArray)
|
|
1325
|
+
return CAFrame.new(out, axis_name: @frame.axis_name, index: @frame.index)
|
|
1326
|
+
end
|
|
1327
|
+
result_frame(named, false)
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
# A key column named in a reduction would be a result column beside the
|
|
1331
|
+
# key it is the group of: the index, or a key column of the result.
|
|
1332
|
+
private def refuse_key_as_result(reduction, names)
|
|
1333
|
+
named = names.map { |n| n.is_a?(String) ? n : nil }.compact & @key_names
|
|
1334
|
+
return if named.empty?
|
|
1335
|
+
hint = reduction == :count ? "; the number of rows of each group is size" : ""
|
|
1336
|
+
raise ArgumentError,
|
|
1337
|
+
"#{reduction}: #{named.first.inspect} is the group key, already in the result#{hint}"
|
|
1338
|
+
end
|
|
1339
|
+
|
|
1340
|
+
# A key column named in a verb computed within each group: the key holds
|
|
1341
|
+
# one value in a group, so there is nothing to compute along it, and a
|
|
1342
|
+
# fill would write to what the groups were made from.
|
|
1343
|
+
private def refuse_key_on_rows(verb, names)
|
|
1344
|
+
named = names.map { |n| n.is_a?(String) ? n : nil }.compact & @key_names
|
|
1345
|
+
return if named.empty?
|
|
1346
|
+
raise ArgumentError,
|
|
1347
|
+
"#{verb}: #{named.first.inspect} is the group key, one value within each group"
|
|
653
1348
|
end
|
|
654
1349
|
|
|
655
1350
|
# One row per group, indexed by the labels; or, with +keep_rows+, the
|
|
@@ -690,6 +1385,24 @@ class GroupedFrame
|
|
|
690
1385
|
end
|
|
691
1386
|
end
|
|
692
1387
|
|
|
1388
|
+
# The columns an aggregate block returned: a Hash of names to CArrays
|
|
1389
|
+
# with one entry per group along their first axis.
|
|
1390
|
+
private def block_columns(out)
|
|
1391
|
+
unless out.is_a?(Hash)
|
|
1392
|
+
raise ArgumentError, "aggregate block must return a Hash (got #{out.class})"
|
|
1393
|
+
end
|
|
1394
|
+
out.each_with_object({}) do |(name, col), cols|
|
|
1395
|
+
name = @frame.__send__(:column_name_arg, name, "aggregate")
|
|
1396
|
+
unless col.is_a?(CArray) && col.ndim >= 1 && col.shape[0] == ngroup
|
|
1397
|
+
got = col.is_a?(CArray) ? "shape #{col.shape.inspect}" : col.class.to_s
|
|
1398
|
+
raise ArgumentError,
|
|
1399
|
+
"aggregate: #{name.inspect} must be a CArray with one value per group " \
|
|
1400
|
+
"(#{ngroup}), got #{got}"
|
|
1401
|
+
end
|
|
1402
|
+
cols[name] = col
|
|
1403
|
+
end
|
|
1404
|
+
end
|
|
1405
|
+
|
|
693
1406
|
private def per_group_column(in_name, proc)
|
|
694
1407
|
results = []
|
|
695
1408
|
each_group_slice(in_name) { |slice| results << proc.call(slice) }
|
|
@@ -751,7 +1464,7 @@ class GroupedFrame
|
|
|
751
1464
|
end
|
|
752
1465
|
|
|
753
1466
|
private def group_perm
|
|
754
|
-
@group_perm ||= @cat.
|
|
1467
|
+
@group_perm ||= @cat.group_addr
|
|
755
1468
|
end
|
|
756
1469
|
|
|
757
1470
|
private def group_bounds
|