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.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +274 -0
  3. data/Cargo.lock +1465 -867
  4. data/Cargo.toml +3 -0
  5. data/LICENSE.txt +2 -2
  6. data/README.md +87 -37
  7. data/ext/polars/Cargo.toml +47 -16
  8. data/ext/polars/src/c_api/allocator.rs +7 -0
  9. data/ext/polars/src/c_api/mod.rs +1 -0
  10. data/ext/polars/src/catalog/mod.rs +1 -0
  11. data/ext/polars/src/catalog/unity.rs +470 -0
  12. data/ext/polars/src/conversion/any_value.rs +99 -84
  13. data/ext/polars/src/conversion/categorical.rs +30 -0
  14. data/ext/polars/src/conversion/chunked_array.rs +71 -62
  15. data/ext/polars/src/conversion/datetime.rs +63 -0
  16. data/ext/polars/src/conversion/mod.rs +796 -312
  17. data/ext/polars/src/dataframe/construction.rs +6 -18
  18. data/ext/polars/src/dataframe/export.rs +30 -39
  19. data/ext/polars/src/dataframe/general.rs +294 -362
  20. data/ext/polars/src/dataframe/io.rs +33 -150
  21. data/ext/polars/src/dataframe/map.rs +175 -0
  22. data/ext/polars/src/dataframe/mod.rs +37 -5
  23. data/ext/polars/src/dataframe/serde.rs +23 -8
  24. data/ext/polars/src/error.rs +44 -7
  25. data/ext/polars/src/exceptions.rs +21 -8
  26. data/ext/polars/src/expr/array.rs +86 -22
  27. data/ext/polars/src/expr/binary.rs +50 -1
  28. data/ext/polars/src/expr/bitwise.rs +39 -0
  29. data/ext/polars/src/expr/categorical.rs +20 -0
  30. data/ext/polars/src/expr/datatype.rs +51 -0
  31. data/ext/polars/src/expr/datetime.rs +99 -41
  32. data/ext/polars/src/expr/extension.rs +12 -0
  33. data/ext/polars/src/expr/general.rs +252 -128
  34. data/ext/polars/src/expr/list.rs +56 -60
  35. data/ext/polars/src/expr/meta.rs +30 -35
  36. data/ext/polars/src/expr/mod.rs +28 -6
  37. data/ext/polars/src/expr/name.rs +29 -14
  38. data/ext/polars/src/expr/rolling.rs +111 -3
  39. data/ext/polars/src/expr/selector.rs +219 -0
  40. data/ext/polars/src/expr/serde.rs +28 -0
  41. data/ext/polars/src/expr/string.rs +118 -20
  42. data/ext/polars/src/expr/struct.rs +14 -1
  43. data/ext/polars/src/file.rs +194 -86
  44. data/ext/polars/src/functions/aggregation.rs +13 -12
  45. data/ext/polars/src/functions/business.rs +2 -3
  46. data/ext/polars/src/functions/eager.rs +3 -2
  47. data/ext/polars/src/functions/io.rs +90 -18
  48. data/ext/polars/src/functions/lazy.rs +267 -118
  49. data/ext/polars/src/functions/meta.rs +8 -7
  50. data/ext/polars/src/functions/misc.rs +1 -1
  51. data/ext/polars/src/functions/mod.rs +2 -1
  52. data/ext/polars/src/functions/range.rs +88 -31
  53. data/ext/polars/src/functions/strings.rs +6 -0
  54. data/ext/polars/src/functions/utils.rs +8 -0
  55. data/ext/polars/src/interop/arrow/mod.rs +52 -1
  56. data/ext/polars/src/interop/arrow/{to_ruby.rs → to_rb.rs} +37 -7
  57. data/ext/polars/src/interop/arrow/to_rust.rs +43 -0
  58. data/ext/polars/src/interop/numo/to_numo_df.rs +1 -1
  59. data/ext/polars/src/interop/numo/to_numo_series.rs +72 -50
  60. data/ext/polars/src/io/cloud_options.rs +107 -0
  61. data/ext/polars/src/io/mod.rs +4 -0
  62. data/ext/polars/src/io/scan_options.rs +113 -0
  63. data/ext/polars/src/io/sink_options.rs +46 -0
  64. data/ext/polars/src/io/sink_output.rs +21 -0
  65. data/ext/polars/src/lazyframe/exitable.rs +39 -0
  66. data/ext/polars/src/lazyframe/general.rs +846 -368
  67. data/ext/polars/src/lazyframe/mod.rs +58 -5
  68. data/ext/polars/src/lazyframe/optflags.rs +59 -0
  69. data/ext/polars/src/lazyframe/serde.rs +36 -4
  70. data/ext/polars/src/lazyframe/sink.rs +46 -0
  71. data/ext/polars/src/lazygroupby.rs +38 -9
  72. data/ext/polars/src/lib.rs +574 -165
  73. data/ext/polars/src/map/lazy.rs +44 -74
  74. data/ext/polars/src/map/mod.rs +18 -254
  75. data/ext/polars/src/map/series.rs +241 -1087
  76. data/ext/polars/src/on_startup.rs +192 -9
  77. data/ext/polars/src/prelude.rs +1 -0
  78. data/ext/polars/src/rb_modules.rs +10 -57
  79. data/ext/polars/src/ruby/exceptions.rs +26 -0
  80. data/ext/polars/src/ruby/gvl.rs +104 -0
  81. data/ext/polars/src/ruby/lazy.rs +46 -0
  82. data/ext/polars/src/ruby/mod.rs +11 -0
  83. data/ext/polars/src/ruby/numo.rs +52 -0
  84. data/ext/polars/src/ruby/plan_callback.rs +198 -0
  85. data/ext/polars/src/ruby/rb_modules.rs +16 -0
  86. data/ext/polars/src/ruby/ruby_convert_registry.rs +51 -0
  87. data/ext/polars/src/ruby/ruby_function.rs +11 -0
  88. data/ext/polars/src/ruby/ruby_udf.rs +164 -0
  89. data/ext/polars/src/ruby/thread.rs +65 -0
  90. data/ext/polars/src/ruby/utils.rs +39 -0
  91. data/ext/polars/src/series/aggregation.rs +116 -91
  92. data/ext/polars/src/series/arithmetic.rs +16 -22
  93. data/ext/polars/src/series/comparison.rs +101 -222
  94. data/ext/polars/src/series/construction.rs +80 -70
  95. data/ext/polars/src/series/export.rs +98 -56
  96. data/ext/polars/src/series/general.rs +323 -440
  97. data/ext/polars/src/series/import.rs +22 -5
  98. data/ext/polars/src/series/map.rs +103 -0
  99. data/ext/polars/src/series/mod.rs +57 -15
  100. data/ext/polars/src/series/scatter.rs +139 -82
  101. data/ext/polars/src/sql.rs +16 -9
  102. data/ext/polars/src/testing/frame.rs +31 -0
  103. data/ext/polars/src/testing/mod.rs +5 -0
  104. data/ext/polars/src/testing/series.rs +31 -0
  105. data/ext/polars/src/timeout.rs +105 -0
  106. data/ext/polars/src/utils.rs +105 -4
  107. data/lib/polars/array_expr.rb +500 -22
  108. data/lib/polars/array_name_space.rb +384 -10
  109. data/lib/polars/batched_csv_reader.rb +48 -66
  110. data/lib/polars/binary_expr.rb +217 -0
  111. data/lib/polars/binary_name_space.rb +155 -1
  112. data/lib/polars/cat_expr.rb +224 -0
  113. data/lib/polars/cat_name_space.rb +132 -32
  114. data/lib/polars/catalog/unity/catalog_info.rb +20 -0
  115. data/lib/polars/catalog/unity/column_info.rb +31 -0
  116. data/lib/polars/catalog/unity/namespace_info.rb +21 -0
  117. data/lib/polars/catalog/unity/table_info.rb +50 -0
  118. data/lib/polars/catalog.rb +448 -0
  119. data/lib/polars/collect_batches.rb +22 -0
  120. data/lib/polars/config.rb +3 -3
  121. data/lib/polars/convert.rb +201 -36
  122. data/lib/polars/data_frame.rb +2851 -1017
  123. data/lib/polars/data_frame_plot.rb +173 -0
  124. data/lib/polars/data_type_expr.rb +52 -0
  125. data/lib/polars/data_type_group.rb +6 -0
  126. data/lib/polars/data_types.rb +118 -18
  127. data/lib/polars/date_time_expr.rb +426 -84
  128. data/lib/polars/date_time_name_space.rb +384 -111
  129. data/lib/polars/dynamic_group_by.rb +102 -10
  130. data/lib/polars/exceptions.rb +50 -5
  131. data/lib/polars/expr.rb +2159 -915
  132. data/lib/polars/extension_expr.rb +39 -0
  133. data/lib/polars/extension_name_space.rb +39 -0
  134. data/lib/polars/functions/aggregation/horizontal.rb +11 -6
  135. data/lib/polars/functions/aggregation/vertical.rb +2 -3
  136. data/lib/polars/functions/as_datatype.rb +290 -8
  137. data/lib/polars/functions/business.rb +95 -0
  138. data/lib/polars/functions/col.rb +6 -5
  139. data/lib/polars/functions/datatype.rb +62 -0
  140. data/lib/polars/functions/eager.rb +426 -24
  141. data/lib/polars/functions/escape_regex.rb +21 -0
  142. data/lib/polars/functions/lazy.rb +813 -195
  143. data/lib/polars/functions/lit.rb +21 -10
  144. data/lib/polars/functions/range/int_range.rb +74 -2
  145. data/lib/polars/functions/range/linear_space.rb +195 -0
  146. data/lib/polars/functions/range/time_range.rb +1 -1
  147. data/lib/polars/functions/repeat.rb +7 -12
  148. data/lib/polars/functions/whenthen.rb +2 -2
  149. data/lib/polars/group_by.rb +188 -58
  150. data/lib/polars/iceberg_dataset.rb +108 -0
  151. data/lib/polars/in_process_query.rb +37 -0
  152. data/lib/polars/io/cloud.rb +18 -0
  153. data/lib/polars/io/csv.rb +336 -128
  154. data/lib/polars/io/database.rb +19 -4
  155. data/lib/polars/io/delta.rb +134 -0
  156. data/lib/polars/io/iceberg.rb +34 -0
  157. data/lib/polars/io/ipc.rb +63 -63
  158. data/lib/polars/io/json.rb +16 -0
  159. data/lib/polars/io/lines.rb +172 -0
  160. data/lib/polars/io/ndjson.rb +176 -20
  161. data/lib/polars/io/parquet.rb +173 -95
  162. data/lib/polars/io/scan_options.rb +55 -0
  163. data/lib/polars/io/sink_options.rb +27 -0
  164. data/lib/polars/io/utils.rb +17 -0
  165. data/lib/polars/lazy_frame.rb +3017 -622
  166. data/lib/polars/lazy_group_by.rb +436 -2
  167. data/lib/polars/list_expr.rb +551 -59
  168. data/lib/polars/list_name_space.rb +465 -51
  169. data/lib/polars/meta_expr.rb +146 -24
  170. data/lib/polars/name_expr.rb +87 -2
  171. data/lib/polars/query_opt_flags.rb +264 -0
  172. data/lib/polars/rolling_group_by.rb +90 -5
  173. data/lib/polars/scan_cast_options.rb +86 -0
  174. data/lib/polars/schema.rb +128 -0
  175. data/lib/polars/selector.rb +245 -0
  176. data/lib/polars/selectors.rb +1048 -201
  177. data/lib/polars/series.rb +2522 -774
  178. data/lib/polars/series_plot.rb +72 -0
  179. data/lib/polars/slice.rb +1 -1
  180. data/lib/polars/sql_context.rb +13 -6
  181. data/lib/polars/string_cache.rb +19 -72
  182. data/lib/polars/string_expr.rb +561 -107
  183. data/lib/polars/string_name_space.rb +781 -109
  184. data/lib/polars/struct_expr.rb +139 -18
  185. data/lib/polars/struct_name_space.rb +19 -1
  186. data/lib/polars/testing.rb +24 -273
  187. data/lib/polars/utils/constants.rb +2 -0
  188. data/lib/polars/utils/construction/data_frame.rb +410 -0
  189. data/lib/polars/utils/construction/series.rb +350 -0
  190. data/lib/polars/utils/construction/utils.rb +9 -0
  191. data/lib/polars/utils/convert.rb +18 -8
  192. data/lib/polars/utils/deprecation.rb +11 -0
  193. data/lib/polars/utils/parse.rb +62 -9
  194. data/lib/polars/utils/reduce_balanced.rb +43 -0
  195. data/lib/polars/utils/serde.rb +22 -0
  196. data/lib/polars/utils/unstable.rb +19 -0
  197. data/lib/polars/utils/various.rb +86 -1
  198. data/lib/polars/utils.rb +63 -48
  199. data/lib/polars/version.rb +1 -1
  200. data/lib/polars.rb +85 -2
  201. metadata +80 -28
  202. data/ext/polars/src/allocator.rs +0 -13
  203. data/ext/polars/src/batched_csv.rs +0 -138
  204. data/ext/polars/src/functions/string_cache.rs +0 -25
  205. data/ext/polars/src/map/dataframe.rs +0 -338
  206. data/lib/polars/plot.rb +0 -109
@@ -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: false)
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(no_optimization: true)
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
- # Apply a custom/user-defined function (UDF) over the groups as a sub-DataFrame.
70
+ # Filter groups with a list of predicates after aggregation.
69
71
  #
70
- # Implementing logic using a Ruby function is almost always _significantly_
71
- # slower and more memory intensive than implementing the same logic using
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
- # Wherever possible you should strongly prefer the native expression API
80
- # to achieve the best performance.
75
+ # This method can be chained and all conditions will be combined using `&`.
81
76
  #
82
- # @return [DataFrame]
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
- # "id" => [0, 1, 2, 3, 4],
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("color").apply { |group_df| group_df.sample(2) }
90
+ # df.group_by("a").having(Polars.len > 1).agg
93
91
  # # =>
94
- # # shape: (4, 3)
95
- # # ┌─────┬───────┬──────────┐
96
- # # │ id ┆ color ┆ shape
97
- # # │ --- ┆ --- ┆ ---
98
- # # │ i64 ┆ str str
99
- # # ╞═════╪═══════╪══════════╡
100
- # # │ 1 ┆ green ┆ triangle
101
- # # │ 2 ┆ green ┆ square
102
- # # │ 4 ┆ red ┆ square │
103
- # # │ 3 ┆ red ┆ triangle │
104
- # # └─────┴───────┴──────────┘
105
- # def apply(&f)
106
- # _dataframe_class._from_rbdf(_df.group_by_apply(by, f))
107
- # end
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
- @df.lazy
205
- .group_by(@by, maintain_order: @maintain_order)
206
+ _lgb
206
207
  .agg(*aggs, **named_aggs)
207
- .collect(no_optimization: true)
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
- @df.lazy
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
- @df.lazy
307
- .group_by(@by, maintain_order: @maintain_order)
308
- .tail(n)
309
- .collect(no_optimization: true)
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(Polars.all.first)
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(Polars.all.last)
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
- # Plot data.
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
- @df.plot(*args, **options, group: @by)
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