polars-df 0.15.0 → 0.26.0
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 +274 -0
- data/Cargo.lock +1465 -867
- data/Cargo.toml +3 -0
- data/LICENSE.txt +2 -2
- data/README.md +87 -37
- data/ext/polars/Cargo.toml +47 -16
- data/ext/polars/src/c_api/allocator.rs +7 -0
- data/ext/polars/src/c_api/mod.rs +1 -0
- data/ext/polars/src/catalog/mod.rs +1 -0
- data/ext/polars/src/catalog/unity.rs +470 -0
- data/ext/polars/src/conversion/any_value.rs +99 -84
- data/ext/polars/src/conversion/categorical.rs +30 -0
- data/ext/polars/src/conversion/chunked_array.rs +71 -62
- data/ext/polars/src/conversion/datetime.rs +63 -0
- data/ext/polars/src/conversion/mod.rs +796 -312
- data/ext/polars/src/dataframe/construction.rs +6 -18
- data/ext/polars/src/dataframe/export.rs +30 -39
- data/ext/polars/src/dataframe/general.rs +294 -362
- data/ext/polars/src/dataframe/io.rs +33 -150
- data/ext/polars/src/dataframe/map.rs +175 -0
- data/ext/polars/src/dataframe/mod.rs +37 -5
- data/ext/polars/src/dataframe/serde.rs +23 -8
- data/ext/polars/src/error.rs +44 -7
- data/ext/polars/src/exceptions.rs +21 -8
- data/ext/polars/src/expr/array.rs +86 -22
- data/ext/polars/src/expr/binary.rs +50 -1
- data/ext/polars/src/expr/bitwise.rs +39 -0
- data/ext/polars/src/expr/categorical.rs +20 -0
- data/ext/polars/src/expr/datatype.rs +51 -0
- data/ext/polars/src/expr/datetime.rs +99 -41
- data/ext/polars/src/expr/extension.rs +12 -0
- data/ext/polars/src/expr/general.rs +252 -128
- data/ext/polars/src/expr/list.rs +56 -60
- data/ext/polars/src/expr/meta.rs +30 -35
- data/ext/polars/src/expr/mod.rs +28 -6
- data/ext/polars/src/expr/name.rs +29 -14
- data/ext/polars/src/expr/rolling.rs +111 -3
- data/ext/polars/src/expr/selector.rs +219 -0
- data/ext/polars/src/expr/serde.rs +28 -0
- data/ext/polars/src/expr/string.rs +118 -20
- data/ext/polars/src/expr/struct.rs +14 -1
- data/ext/polars/src/file.rs +194 -86
- data/ext/polars/src/functions/aggregation.rs +13 -12
- data/ext/polars/src/functions/business.rs +2 -3
- data/ext/polars/src/functions/eager.rs +3 -2
- data/ext/polars/src/functions/io.rs +90 -18
- data/ext/polars/src/functions/lazy.rs +267 -118
- data/ext/polars/src/functions/meta.rs +8 -7
- data/ext/polars/src/functions/misc.rs +1 -1
- data/ext/polars/src/functions/mod.rs +2 -1
- data/ext/polars/src/functions/range.rs +88 -31
- data/ext/polars/src/functions/strings.rs +6 -0
- data/ext/polars/src/functions/utils.rs +8 -0
- data/ext/polars/src/interop/arrow/mod.rs +52 -1
- data/ext/polars/src/interop/arrow/{to_ruby.rs → to_rb.rs} +37 -7
- data/ext/polars/src/interop/arrow/to_rust.rs +43 -0
- data/ext/polars/src/interop/numo/to_numo_df.rs +1 -1
- data/ext/polars/src/interop/numo/to_numo_series.rs +72 -50
- data/ext/polars/src/io/cloud_options.rs +107 -0
- data/ext/polars/src/io/mod.rs +4 -0
- data/ext/polars/src/io/scan_options.rs +113 -0
- data/ext/polars/src/io/sink_options.rs +46 -0
- data/ext/polars/src/io/sink_output.rs +21 -0
- data/ext/polars/src/lazyframe/exitable.rs +39 -0
- data/ext/polars/src/lazyframe/general.rs +846 -368
- data/ext/polars/src/lazyframe/mod.rs +58 -5
- data/ext/polars/src/lazyframe/optflags.rs +59 -0
- data/ext/polars/src/lazyframe/serde.rs +36 -4
- data/ext/polars/src/lazyframe/sink.rs +46 -0
- data/ext/polars/src/lazygroupby.rs +38 -9
- data/ext/polars/src/lib.rs +574 -165
- data/ext/polars/src/map/lazy.rs +44 -74
- data/ext/polars/src/map/mod.rs +18 -254
- data/ext/polars/src/map/series.rs +241 -1087
- data/ext/polars/src/on_startup.rs +192 -9
- data/ext/polars/src/prelude.rs +1 -0
- data/ext/polars/src/rb_modules.rs +10 -57
- data/ext/polars/src/ruby/exceptions.rs +26 -0
- data/ext/polars/src/ruby/gvl.rs +104 -0
- data/ext/polars/src/ruby/lazy.rs +46 -0
- data/ext/polars/src/ruby/mod.rs +11 -0
- data/ext/polars/src/ruby/numo.rs +52 -0
- data/ext/polars/src/ruby/plan_callback.rs +198 -0
- data/ext/polars/src/ruby/rb_modules.rs +16 -0
- data/ext/polars/src/ruby/ruby_convert_registry.rs +51 -0
- data/ext/polars/src/ruby/ruby_function.rs +11 -0
- data/ext/polars/src/ruby/ruby_udf.rs +164 -0
- data/ext/polars/src/ruby/thread.rs +65 -0
- data/ext/polars/src/ruby/utils.rs +39 -0
- data/ext/polars/src/series/aggregation.rs +116 -91
- data/ext/polars/src/series/arithmetic.rs +16 -22
- data/ext/polars/src/series/comparison.rs +101 -222
- data/ext/polars/src/series/construction.rs +80 -70
- data/ext/polars/src/series/export.rs +98 -56
- data/ext/polars/src/series/general.rs +323 -440
- data/ext/polars/src/series/import.rs +22 -5
- data/ext/polars/src/series/map.rs +103 -0
- data/ext/polars/src/series/mod.rs +57 -15
- data/ext/polars/src/series/scatter.rs +139 -82
- data/ext/polars/src/sql.rs +16 -9
- data/ext/polars/src/testing/frame.rs +31 -0
- data/ext/polars/src/testing/mod.rs +5 -0
- data/ext/polars/src/testing/series.rs +31 -0
- data/ext/polars/src/timeout.rs +105 -0
- data/ext/polars/src/utils.rs +105 -4
- data/lib/polars/array_expr.rb +500 -22
- data/lib/polars/array_name_space.rb +384 -10
- data/lib/polars/batched_csv_reader.rb +48 -66
- data/lib/polars/binary_expr.rb +217 -0
- data/lib/polars/binary_name_space.rb +155 -1
- data/lib/polars/cat_expr.rb +224 -0
- data/lib/polars/cat_name_space.rb +132 -32
- data/lib/polars/catalog/unity/catalog_info.rb +20 -0
- data/lib/polars/catalog/unity/column_info.rb +31 -0
- data/lib/polars/catalog/unity/namespace_info.rb +21 -0
- data/lib/polars/catalog/unity/table_info.rb +50 -0
- data/lib/polars/catalog.rb +448 -0
- data/lib/polars/collect_batches.rb +22 -0
- data/lib/polars/config.rb +3 -3
- data/lib/polars/convert.rb +201 -36
- data/lib/polars/data_frame.rb +2851 -1017
- data/lib/polars/data_frame_plot.rb +173 -0
- data/lib/polars/data_type_expr.rb +52 -0
- data/lib/polars/data_type_group.rb +6 -0
- data/lib/polars/data_types.rb +118 -18
- data/lib/polars/date_time_expr.rb +426 -84
- data/lib/polars/date_time_name_space.rb +384 -111
- data/lib/polars/dynamic_group_by.rb +102 -10
- data/lib/polars/exceptions.rb +50 -5
- data/lib/polars/expr.rb +2159 -915
- data/lib/polars/extension_expr.rb +39 -0
- data/lib/polars/extension_name_space.rb +39 -0
- data/lib/polars/functions/aggregation/horizontal.rb +11 -6
- data/lib/polars/functions/aggregation/vertical.rb +2 -3
- data/lib/polars/functions/as_datatype.rb +290 -8
- data/lib/polars/functions/business.rb +95 -0
- data/lib/polars/functions/col.rb +6 -5
- data/lib/polars/functions/datatype.rb +62 -0
- data/lib/polars/functions/eager.rb +426 -24
- data/lib/polars/functions/escape_regex.rb +21 -0
- data/lib/polars/functions/lazy.rb +813 -195
- data/lib/polars/functions/lit.rb +21 -10
- data/lib/polars/functions/range/int_range.rb +74 -2
- data/lib/polars/functions/range/linear_space.rb +195 -0
- data/lib/polars/functions/range/time_range.rb +1 -1
- data/lib/polars/functions/repeat.rb +7 -12
- data/lib/polars/functions/whenthen.rb +2 -2
- data/lib/polars/group_by.rb +188 -58
- data/lib/polars/iceberg_dataset.rb +108 -0
- data/lib/polars/in_process_query.rb +37 -0
- data/lib/polars/io/cloud.rb +18 -0
- data/lib/polars/io/csv.rb +336 -128
- data/lib/polars/io/database.rb +19 -4
- data/lib/polars/io/delta.rb +134 -0
- data/lib/polars/io/iceberg.rb +34 -0
- data/lib/polars/io/ipc.rb +63 -63
- data/lib/polars/io/json.rb +16 -0
- data/lib/polars/io/lines.rb +172 -0
- data/lib/polars/io/ndjson.rb +176 -20
- data/lib/polars/io/parquet.rb +173 -95
- data/lib/polars/io/scan_options.rb +55 -0
- data/lib/polars/io/sink_options.rb +27 -0
- data/lib/polars/io/utils.rb +17 -0
- data/lib/polars/lazy_frame.rb +3017 -622
- data/lib/polars/lazy_group_by.rb +436 -2
- data/lib/polars/list_expr.rb +551 -59
- data/lib/polars/list_name_space.rb +465 -51
- data/lib/polars/meta_expr.rb +146 -24
- data/lib/polars/name_expr.rb +87 -2
- data/lib/polars/query_opt_flags.rb +264 -0
- data/lib/polars/rolling_group_by.rb +90 -5
- data/lib/polars/scan_cast_options.rb +86 -0
- data/lib/polars/schema.rb +128 -0
- data/lib/polars/selector.rb +245 -0
- data/lib/polars/selectors.rb +1048 -201
- data/lib/polars/series.rb +2522 -774
- data/lib/polars/series_plot.rb +72 -0
- data/lib/polars/slice.rb +1 -1
- data/lib/polars/sql_context.rb +13 -6
- data/lib/polars/string_cache.rb +19 -72
- data/lib/polars/string_expr.rb +561 -107
- data/lib/polars/string_name_space.rb +781 -109
- data/lib/polars/struct_expr.rb +139 -18
- data/lib/polars/struct_name_space.rb +19 -1
- data/lib/polars/testing.rb +24 -273
- data/lib/polars/utils/constants.rb +2 -0
- data/lib/polars/utils/construction/data_frame.rb +410 -0
- data/lib/polars/utils/construction/series.rb +350 -0
- data/lib/polars/utils/construction/utils.rb +9 -0
- data/lib/polars/utils/convert.rb +18 -8
- data/lib/polars/utils/deprecation.rb +11 -0
- data/lib/polars/utils/parse.rb +62 -9
- data/lib/polars/utils/reduce_balanced.rb +43 -0
- data/lib/polars/utils/serde.rb +22 -0
- data/lib/polars/utils/unstable.rb +19 -0
- data/lib/polars/utils/various.rb +86 -1
- data/lib/polars/utils.rb +63 -48
- data/lib/polars/version.rb +1 -1
- data/lib/polars.rb +85 -2
- metadata +80 -28
- data/ext/polars/src/allocator.rs +0 -13
- data/ext/polars/src/batched_csv.rs +0 -138
- data/ext/polars/src/functions/string_cache.rs +0 -25
- data/ext/polars/src/map/dataframe.rs +0 -338
- data/lib/polars/plot.rb +0 -109
data/lib/polars/group_by.rb
CHANGED
|
@@ -2,10 +2,12 @@ module Polars
|
|
|
2
2
|
# Starts a new GroupBy operation.
|
|
3
3
|
class GroupBy
|
|
4
4
|
# @private
|
|
5
|
-
def initialize(df, by, maintain_order
|
|
5
|
+
def initialize(df, *by, maintain_order:, predicates:, **named_by)
|
|
6
6
|
@df = df
|
|
7
7
|
@by = by
|
|
8
|
+
@named_by = named_by
|
|
8
9
|
@maintain_order = maintain_order
|
|
10
|
+
@predicates = predicates
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
# Allows iteration over the groups of the group by operation.
|
|
@@ -16,7 +18,7 @@ module Polars
|
|
|
16
18
|
# df = Polars::DataFrame.new({"foo" => ["a", "a", "b"], "bar" => [1, 2, 3]})
|
|
17
19
|
# df.group_by("foo", maintain_order: true).each.to_h
|
|
18
20
|
# # =>
|
|
19
|
-
# # {"a"=>shape: (2, 2)
|
|
21
|
+
# # {["a"]=>shape: (2, 2)
|
|
20
22
|
# # ┌─────┬─────┐
|
|
21
23
|
# # │ foo ┆ bar │
|
|
22
24
|
# # │ --- ┆ --- │
|
|
@@ -24,7 +26,7 @@ module Polars
|
|
|
24
26
|
# # ╞═════╪═════╡
|
|
25
27
|
# # │ a ┆ 1 │
|
|
26
28
|
# # │ a ┆ 2 │
|
|
27
|
-
# # └─────┴─────┘, "b"=>shape: (1, 2)
|
|
29
|
+
# # └─────┴─────┘, ["b"]=>shape: (1, 2)
|
|
28
30
|
# # ┌─────┬─────┐
|
|
29
31
|
# # │ foo ┆ bar │
|
|
30
32
|
# # │ --- ┆ --- │
|
|
@@ -39,9 +41,9 @@ module Polars
|
|
|
39
41
|
groups_df =
|
|
40
42
|
@df.lazy
|
|
41
43
|
.with_row_index(name: temp_col)
|
|
42
|
-
.group_by(@by, maintain_order: @maintain_order)
|
|
44
|
+
.group_by(@by, **@named_by, maintain_order: @maintain_order)
|
|
43
45
|
.agg(Polars.col(temp_col))
|
|
44
|
-
.collect(
|
|
46
|
+
.collect(optimizations: QueryOptFlags.none)
|
|
45
47
|
|
|
46
48
|
group_names = groups_df.select(Polars.all.exclude(temp_col))
|
|
47
49
|
|
|
@@ -65,46 +67,46 @@ module Polars
|
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
69
|
|
|
68
|
-
#
|
|
70
|
+
# Filter groups with a list of predicates after aggregation.
|
|
69
71
|
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# the native expression API because:
|
|
73
|
-
|
|
74
|
-
# - The native expression engine runs in Rust; UDFs run in Ruby.
|
|
75
|
-
# - Use of Ruby UDFs forces the DataFrame to be materialized in memory.
|
|
76
|
-
# - Polars-native expressions can be parallelised (UDFs cannot).
|
|
77
|
-
# - Polars-native expressions can be logically optimised (UDFs cannot).
|
|
72
|
+
# Using this method is equivalent to adding the predicates to the aggregation and
|
|
73
|
+
# filtering afterwards.
|
|
78
74
|
#
|
|
79
|
-
#
|
|
80
|
-
# to achieve the best performance.
|
|
75
|
+
# This method can be chained and all conditions will be combined using `&`.
|
|
81
76
|
#
|
|
82
|
-
# @
|
|
77
|
+
# @param predicates [Array]
|
|
78
|
+
# Expressions that evaluate to a boolean value for each group. Typically, this
|
|
79
|
+
# requires the use of an aggregation function. Multiple predicates are
|
|
80
|
+
# combined using `&`.
|
|
81
|
+
#
|
|
82
|
+
# @return [GroupBy]
|
|
83
83
|
#
|
|
84
84
|
# @example
|
|
85
85
|
# df = Polars::DataFrame.new(
|
|
86
86
|
# {
|
|
87
|
-
# "
|
|
88
|
-
# "color" => ["red", "green", "green", "red", "red"],
|
|
89
|
-
# "shape" => ["square", "triangle", "square", "triangle", "square"]
|
|
87
|
+
# "a" => ["a", "b", "a", "b", "c"]
|
|
90
88
|
# }
|
|
91
89
|
# )
|
|
92
|
-
# df.group_by("
|
|
90
|
+
# df.group_by("a").having(Polars.len > 1).agg
|
|
93
91
|
# # =>
|
|
94
|
-
# # shape: (
|
|
95
|
-
# #
|
|
96
|
-
# # │
|
|
97
|
-
# # │ ---
|
|
98
|
-
# # │
|
|
99
|
-
# #
|
|
100
|
-
# # │
|
|
101
|
-
# # │
|
|
102
|
-
# #
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
92
|
+
# # shape: (2, 1)
|
|
93
|
+
# # ┌─────┐
|
|
94
|
+
# # │ a │
|
|
95
|
+
# # │ --- │
|
|
96
|
+
# # │ str │
|
|
97
|
+
# # ╞═════╡
|
|
98
|
+
# # │ b │
|
|
99
|
+
# # │ a │
|
|
100
|
+
# # └─────┘
|
|
101
|
+
def having(*predicates)
|
|
102
|
+
GroupBy.new(
|
|
103
|
+
@df,
|
|
104
|
+
*@by,
|
|
105
|
+
maintain_order: @maintain_order,
|
|
106
|
+
predicates: Utils._chain_predicates(@predicates, predicates),
|
|
107
|
+
**@named_by
|
|
108
|
+
)
|
|
109
|
+
end
|
|
108
110
|
|
|
109
111
|
# Compute aggregations for each group of a group by operation.
|
|
110
112
|
#
|
|
@@ -201,10 +203,71 @@ module Polars
|
|
|
201
203
|
# # │ b ┆ 5 ┆ 10.0 │
|
|
202
204
|
# # └─────┴───────┴────────────────┘
|
|
203
205
|
def agg(*aggs, **named_aggs)
|
|
204
|
-
|
|
205
|
-
.group_by(@by, maintain_order: @maintain_order)
|
|
206
|
+
_lgb
|
|
206
207
|
.agg(*aggs, **named_aggs)
|
|
207
|
-
.collect(
|
|
208
|
+
.collect(optimizations: QueryOptFlags.none)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Apply a custom/user-defined function (UDF) over the groups as a sub-DataFrame.
|
|
212
|
+
#
|
|
213
|
+
# @note
|
|
214
|
+
# This method is much slower than the native expressions API.
|
|
215
|
+
# Only use it if you cannot implement your logic otherwise.
|
|
216
|
+
#
|
|
217
|
+
# Implementing logic using a Ruby function is almost always *significantly*
|
|
218
|
+
# slower and more memory intensive than implementing the same logic using
|
|
219
|
+
# the native expression API because:
|
|
220
|
+
#
|
|
221
|
+
# - The native expression engine runs in Rust; UDFs run in Ruby.
|
|
222
|
+
# - Use of Ruby UDFs forces the DataFrame to be materialized in memory.
|
|
223
|
+
# - Polars-native expressions can be parallelised (UDFs cannot).
|
|
224
|
+
# - Polars-native expressions can be logically optimised (UDFs cannot).
|
|
225
|
+
#
|
|
226
|
+
# Wherever possible you should strongly prefer the native expression API
|
|
227
|
+
# to achieve the best performance.
|
|
228
|
+
#
|
|
229
|
+
# @return [DataFrame]
|
|
230
|
+
#
|
|
231
|
+
# @example
|
|
232
|
+
# df = Polars::DataFrame.new(
|
|
233
|
+
# {
|
|
234
|
+
# "id" => [0, 1, 2, 3, 4],
|
|
235
|
+
# "color" => ["red", "green", "green", "red", "red"],
|
|
236
|
+
# "shape" => ["square", "triangle", "square", "triangle", "square"]
|
|
237
|
+
# }
|
|
238
|
+
# )
|
|
239
|
+
# df.group_by("color").map_groups { |group_df| group_df.sample(n: 2) }
|
|
240
|
+
# # =>
|
|
241
|
+
# # shape: (4, 3)
|
|
242
|
+
# # ┌─────┬───────┬──────────┐
|
|
243
|
+
# # │ id ┆ color ┆ shape │
|
|
244
|
+
# # │ --- ┆ --- ┆ --- │
|
|
245
|
+
# # │ i64 ┆ str ┆ str │
|
|
246
|
+
# # ╞═════╪═══════╪══════════╡
|
|
247
|
+
# # │ 1 ┆ green ┆ triangle │
|
|
248
|
+
# # │ 2 ┆ green ┆ square │
|
|
249
|
+
# # │ 4 ┆ red ┆ square │
|
|
250
|
+
# # │ 3 ┆ red ┆ triangle │
|
|
251
|
+
# # └─────┴───────┴──────────┘
|
|
252
|
+
def map_groups(&function)
|
|
253
|
+
if @predicates&.any?
|
|
254
|
+
msg = "cannot call `map_groups` when filtering groups with `having`"
|
|
255
|
+
raise TypeError, msg
|
|
256
|
+
end
|
|
257
|
+
if @named_by&.any?
|
|
258
|
+
msg = "cannot call `map_groups` when grouping by named expressions"
|
|
259
|
+
raise TypeError, msg
|
|
260
|
+
end
|
|
261
|
+
if !@by.all? { |c| Utils.strlike?(c) }
|
|
262
|
+
msg = "cannot call `map_groups` when grouping by an expression"
|
|
263
|
+
raise TypeError, msg
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
by_strs = @by.map(&:to_s)
|
|
267
|
+
|
|
268
|
+
@df.class._from_rbdf(
|
|
269
|
+
@df._df.group_by_map_groups(by_strs, function, @maintain_order)
|
|
270
|
+
)
|
|
208
271
|
end
|
|
209
272
|
|
|
210
273
|
# Get the first `n` rows of each group.
|
|
@@ -252,10 +315,7 @@ module Polars
|
|
|
252
315
|
# # │ c ┆ 2 │
|
|
253
316
|
# # └─────────┴─────┘
|
|
254
317
|
def head(n = 5)
|
|
255
|
-
|
|
256
|
-
.group_by(@by, maintain_order: @maintain_order)
|
|
257
|
-
.head(n)
|
|
258
|
-
.collect(no_optimization: true)
|
|
318
|
+
_lgb.head(n).collect(optimizations: QueryOptFlags._eager)
|
|
259
319
|
end
|
|
260
320
|
|
|
261
321
|
# Get the last `n` rows of each group.
|
|
@@ -303,14 +363,78 @@ module Polars
|
|
|
303
363
|
# # │ c ┆ 4 │
|
|
304
364
|
# # └─────────┴─────┘
|
|
305
365
|
def tail(n = 5)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
366
|
+
_lgb.tail(n).collect(optimizations: QueryOptFlags._eager)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Aggregate the groups into Series.
|
|
370
|
+
#
|
|
371
|
+
# @return [DataFrame]
|
|
372
|
+
#
|
|
373
|
+
# @example
|
|
374
|
+
# df = Polars::DataFrame.new({"a" => ["one", "two", "one", "two"], "b" => [1, 2, 3, 4]})
|
|
375
|
+
# df.group_by("a", maintain_order: true).all
|
|
376
|
+
# # =>
|
|
377
|
+
# # shape: (2, 2)
|
|
378
|
+
# # ┌─────┬───────────┐
|
|
379
|
+
# # │ a ┆ b │
|
|
380
|
+
# # │ --- ┆ --- │
|
|
381
|
+
# # │ str ┆ list[i64] │
|
|
382
|
+
# # ╞═════╪═══════════╡
|
|
383
|
+
# # │ one ┆ [1, 3] │
|
|
384
|
+
# # │ two ┆ [2, 4] │
|
|
385
|
+
# # └─────┴───────────┘
|
|
386
|
+
def all
|
|
387
|
+
agg(F.all)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Return the number of rows in each group.
|
|
391
|
+
#
|
|
392
|
+
# @param name [String]
|
|
393
|
+
# Assign a name to the resulting column; if unset, defaults to "len".
|
|
394
|
+
#
|
|
395
|
+
# @return [DataFrame]
|
|
396
|
+
#
|
|
397
|
+
# @example
|
|
398
|
+
# df = Polars::DataFrame.new({"a" => ["Apple", "Apple", "Orange"], "b" => [1, nil, 2]})
|
|
399
|
+
# df.group_by("a").len
|
|
400
|
+
# # =>
|
|
401
|
+
# # shape: (2, 2)
|
|
402
|
+
# # ┌────────┬─────┐
|
|
403
|
+
# # │ a ┆ len │
|
|
404
|
+
# # │ --- ┆ --- │
|
|
405
|
+
# # │ str ┆ u32 │
|
|
406
|
+
# # ╞════════╪═════╡
|
|
407
|
+
# # │ Apple ┆ 2 │
|
|
408
|
+
# # │ Orange ┆ 1 │
|
|
409
|
+
# # └────────┴─────┘
|
|
410
|
+
#
|
|
411
|
+
# @example
|
|
412
|
+
# df.group_by("a").len(name: "n")
|
|
413
|
+
# # =>
|
|
414
|
+
# # shape: (2, 2)
|
|
415
|
+
# # ┌────────┬─────┐
|
|
416
|
+
# # │ a ┆ n │
|
|
417
|
+
# # │ --- ┆ --- │
|
|
418
|
+
# # │ str ┆ u32 │
|
|
419
|
+
# # ╞════════╪═════╡
|
|
420
|
+
# # │ Apple ┆ 2 │
|
|
421
|
+
# # │ Orange ┆ 1 │
|
|
422
|
+
# # └────────┴─────┘
|
|
423
|
+
def len(name: nil)
|
|
424
|
+
len_expr = F.len
|
|
425
|
+
if !name.nil?
|
|
426
|
+
len_expr = len_expr.alias(name)
|
|
427
|
+
end
|
|
428
|
+
agg(len_expr)
|
|
310
429
|
end
|
|
311
430
|
|
|
312
431
|
# Aggregate the first values in the group.
|
|
313
432
|
#
|
|
433
|
+
# @param ignore_nulls [Boolean]
|
|
434
|
+
# Ignore null values (default `false`).
|
|
435
|
+
# If set to `true`, the first non-null value for each aggregation is returned,
|
|
436
|
+
# otherwise `nil` is returned if no non-null value exists.
|
|
437
|
+
#
|
|
314
438
|
# @return [DataFrame]
|
|
315
439
|
#
|
|
316
440
|
# @example
|
|
@@ -334,12 +458,17 @@ module Polars
|
|
|
334
458
|
# # │ Orange ┆ 2 ┆ 0.5 ┆ true │
|
|
335
459
|
# # │ Banana ┆ 4 ┆ 13.0 ┆ false │
|
|
336
460
|
# # └────────┴─────┴──────┴───────┘
|
|
337
|
-
def first
|
|
338
|
-
agg(
|
|
461
|
+
def first(ignore_nulls: false)
|
|
462
|
+
agg(F.all.first(ignore_nulls: ignore_nulls))
|
|
339
463
|
end
|
|
340
464
|
|
|
341
465
|
# Aggregate the last values in the group.
|
|
342
466
|
#
|
|
467
|
+
# @param ignore_nulls [Boolean]
|
|
468
|
+
# Ignore null values (default `false`).
|
|
469
|
+
# If set to `true`, the last non-null value for each aggregation is returned,
|
|
470
|
+
# otherwise `nil` is returned if no non-null value exists.
|
|
471
|
+
#
|
|
343
472
|
# @return [DataFrame]
|
|
344
473
|
#
|
|
345
474
|
# @example
|
|
@@ -363,8 +492,8 @@ module Polars
|
|
|
363
492
|
# # │ Orange ┆ 2 ┆ 0.5 ┆ true │
|
|
364
493
|
# # │ Banana ┆ 5 ┆ 14.0 ┆ true │
|
|
365
494
|
# # └────────┴─────┴──────┴───────┘
|
|
366
|
-
def last
|
|
367
|
-
agg(
|
|
495
|
+
def last(ignore_nulls: false)
|
|
496
|
+
agg(F.all.last(ignore_nulls: ignore_nulls))
|
|
368
497
|
end
|
|
369
498
|
|
|
370
499
|
# Reduce the groups to the sum.
|
|
@@ -599,15 +728,16 @@ module Polars
|
|
|
599
728
|
agg(Polars.all.median)
|
|
600
729
|
end
|
|
601
730
|
|
|
602
|
-
|
|
603
|
-
#
|
|
604
|
-
# @return [Vega::LiteChart]
|
|
605
|
-
def plot(*args, **options)
|
|
606
|
-
raise ArgumentError, "Multiple groups not supported" if @by.is_a?(::Array) && @by.size > 1
|
|
607
|
-
# same message as Ruby
|
|
608
|
-
raise ArgumentError, "unknown keyword: :group" if options.key?(:group)
|
|
731
|
+
private
|
|
609
732
|
|
|
610
|
-
|
|
733
|
+
def _lgb
|
|
734
|
+
group_by = @df.lazy.group_by(
|
|
735
|
+
*@by, **@named_by, maintain_order: @maintain_order
|
|
736
|
+
)
|
|
737
|
+
if @predicates&.any?
|
|
738
|
+
return group_by.having(@predicates)
|
|
739
|
+
end
|
|
740
|
+
group_by
|
|
611
741
|
end
|
|
612
742
|
end
|
|
613
743
|
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module Polars
|
|
2
|
+
# @private
|
|
3
|
+
class IcebergDataset
|
|
4
|
+
def initialize(
|
|
5
|
+
source,
|
|
6
|
+
snapshot_id:,
|
|
7
|
+
storage_options:
|
|
8
|
+
)
|
|
9
|
+
@source = source
|
|
10
|
+
@snapshot_id = snapshot_id
|
|
11
|
+
@storage_options = storage_options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_lazyframe
|
|
15
|
+
scan = @source.scan(snapshot_id: @snapshot_id)
|
|
16
|
+
files = scan.plan_files
|
|
17
|
+
|
|
18
|
+
table = scan.table
|
|
19
|
+
snapshot = scan.snapshot
|
|
20
|
+
schema = snapshot ? table.schema_by_id(snapshot[:schema_id]) : table.current_schema
|
|
21
|
+
|
|
22
|
+
if files.empty?
|
|
23
|
+
# TODO improve
|
|
24
|
+
schema =
|
|
25
|
+
schema.fields.to_h do |field|
|
|
26
|
+
dtype =
|
|
27
|
+
case field[:type]
|
|
28
|
+
when "int"
|
|
29
|
+
Polars::Int32
|
|
30
|
+
when "long"
|
|
31
|
+
Polars::Int64
|
|
32
|
+
when "double"
|
|
33
|
+
Polars::Float64
|
|
34
|
+
when "string"
|
|
35
|
+
Polars::String
|
|
36
|
+
when "timestamp"
|
|
37
|
+
Polars::Datetime
|
|
38
|
+
else
|
|
39
|
+
raise Todo
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
[field[:name], dtype]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
LazyFrame.new(schema: schema)
|
|
46
|
+
else
|
|
47
|
+
sources = files.map { |v| v[:data_file_path] }
|
|
48
|
+
|
|
49
|
+
column_mapping = [
|
|
50
|
+
"iceberg-column-mapping",
|
|
51
|
+
arrow_schema(schema)
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
deletion_files = [
|
|
55
|
+
"iceberg-position-delete",
|
|
56
|
+
files.map.with_index
|
|
57
|
+
.select { |v, i| v[:deletes].any? }
|
|
58
|
+
.to_h { |v, i| [i, v[:deletes].map { |d| d[:file_path] }] }
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
scan_options = {
|
|
62
|
+
storage_options: @storage_options,
|
|
63
|
+
cast_options: Polars::ScanCastOptions._default_iceberg,
|
|
64
|
+
missing_columns: "insert",
|
|
65
|
+
extra_columns: "ignore",
|
|
66
|
+
_column_mapping: column_mapping,
|
|
67
|
+
_deletion_files: deletion_files
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
Polars.scan_parquet(sources, **scan_options)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def arrow_schema(schema)
|
|
77
|
+
fields =
|
|
78
|
+
schema.fields.map do |field|
|
|
79
|
+
type =
|
|
80
|
+
case field[:type]
|
|
81
|
+
when "boolean"
|
|
82
|
+
"boolean"
|
|
83
|
+
when "int"
|
|
84
|
+
"int32"
|
|
85
|
+
when "long"
|
|
86
|
+
"int64"
|
|
87
|
+
when "float"
|
|
88
|
+
"float32"
|
|
89
|
+
when "double"
|
|
90
|
+
"float64"
|
|
91
|
+
else
|
|
92
|
+
raise Todo
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
{
|
|
96
|
+
name: field[:name],
|
|
97
|
+
type: type,
|
|
98
|
+
nullable: !field[:required],
|
|
99
|
+
metadata: {
|
|
100
|
+
"PARQUET:field_id" => field[:id].to_s
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
{fields: fields}
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Polars
|
|
2
|
+
# A placeholder for an in process query.
|
|
3
|
+
#
|
|
4
|
+
# This can be used to do something else while a query is running.
|
|
5
|
+
# The queries can be cancelled. You can peek if the query is finished,
|
|
6
|
+
# or you can await the result.
|
|
7
|
+
class InProcessQuery
|
|
8
|
+
# @private
|
|
9
|
+
attr_accessor :_inner
|
|
10
|
+
|
|
11
|
+
def initialize(ipq)
|
|
12
|
+
self._inner = ipq
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Cancel the query at earliest convenience.
|
|
16
|
+
def cancel
|
|
17
|
+
_inner.cancel
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Fetch the result.
|
|
21
|
+
#
|
|
22
|
+
# If it is ready, a materialized DataFrame is returned.
|
|
23
|
+
# If it is not ready it will return `nil`.
|
|
24
|
+
def fetch
|
|
25
|
+
if !(out = _inner.fetch).nil?
|
|
26
|
+
Utils.wrap_df(out)
|
|
27
|
+
else
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Await the result synchronously.
|
|
33
|
+
def fetch_blocking
|
|
34
|
+
Utils.wrap_df(_inner.fetch_blocking)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Polars
|
|
2
|
+
module IO
|
|
3
|
+
private
|
|
4
|
+
|
|
5
|
+
def _init_credential_provider_builder(
|
|
6
|
+
credential_provider,
|
|
7
|
+
source,
|
|
8
|
+
storage_options,
|
|
9
|
+
caller_name
|
|
10
|
+
)
|
|
11
|
+
if credential_provider && credential_provider != "auto"
|
|
12
|
+
raise Todo
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|