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
@@ -97,6 +97,70 @@ module Polars
97
97
  _rbexpr.meta_is_regex_projection
98
98
  end
99
99
 
100
+ # Indicate if this expression only selects columns (optionally with aliasing).
101
+ #
102
+ # This can include bare columns, columns matched by regex or dtype, selectors
103
+ # and exclude ops, and (optionally) column/expression aliasing.
104
+ #
105
+ # @param allow_aliasing [Boolean]
106
+ # If false (default), any aliasing is not considered to be column selection.
107
+ # Set true to allow for column selection that also includes aliasing.
108
+ #
109
+ # @return [Boolean]
110
+ #
111
+ # @example
112
+ # e = Polars.col("foo")
113
+ # e.meta.is_column_selection
114
+ # # => true
115
+ #
116
+ # @example
117
+ # e = Polars.col("foo").alias("bar")
118
+ # e.meta.is_column_selection
119
+ # # => false
120
+ #
121
+ # @example
122
+ # e.meta.is_column_selection(allow_aliasing: true)
123
+ # # => true
124
+ #
125
+ # @example
126
+ # e = Polars.col("foo") * Polars.col("bar")
127
+ # e.meta.is_column_selection
128
+ # # => false
129
+ #
130
+ # @example
131
+ # e = Polars.cs.starts_with("foo")
132
+ # e.meta.is_column_selection
133
+ # # => true
134
+ #
135
+ # @example
136
+ # e = Polars.cs.starts_with("foo").exclude("foo!")
137
+ # e.meta.is_column_selection
138
+ # # => true
139
+ def is_column_selection(allow_aliasing: false)
140
+ _rbexpr.meta_is_column_selection(allow_aliasing)
141
+ end
142
+
143
+ # Indicate if this expression is a literal value (optionally aliased).
144
+ #
145
+ # @param allow_aliasing [Boolean]
146
+ # If false (default), only a bare literal will match.
147
+ # Set true to also allow for aliased literals.
148
+ #
149
+ # @return [Boolean]
150
+ #
151
+ # @example
152
+ # e = Polars.lit(123)
153
+ # e.meta.is_literal
154
+ # # => true
155
+ #
156
+ # @example
157
+ # e = Polars.lit(987.654321).alias("foo")
158
+ # e.meta.is_literal
159
+ # # => false
160
+ def is_literal(allow_aliasing: false)
161
+ _rbexpr.meta_is_literal(allow_aliasing)
162
+ end
163
+
100
164
  # Get the column name that this expression would produce.
101
165
  #
102
166
  # @return [String]
@@ -116,8 +180,13 @@ module Polars
116
180
  # # => "foo"
117
181
  # Polars.len.meta.output_name
118
182
  # # => "len"
119
- def output_name
183
+ def output_name(raise_if_undetermined: true)
120
184
  _rbexpr.meta_output_name
185
+ rescue Polars::ComputeError
186
+ if !raise_if_undetermined
187
+ return nil
188
+ end
189
+ raise
121
190
  end
122
191
 
123
192
  # Pop the latest expression and return the input(s) of the popped expression.
@@ -125,14 +194,14 @@ module Polars
125
194
  # @return [Array]
126
195
  #
127
196
  # @example
128
- # e = Polars.col("foo").alias("bar")
197
+ # e = Polars.col("foo") + Polars.col("bar")
129
198
  # first = e.meta.pop[0]
130
- # _ = first.meta == Polars.col("foo")
131
- # # => true
132
199
  # _ = first.meta == Polars.col("bar")
200
+ # # => true
201
+ # _ = first.meta == Polars.col("foo")
133
202
  # # => false
134
- def pop
135
- _rbexpr.meta_pop.map { |e| Utils.wrap_expr(e) }
203
+ def pop(schema: nil)
204
+ _rbexpr.meta_pop(schema).map { |e| Utils.wrap_expr(e) }
136
205
  end
137
206
 
138
207
  # Get a list with the root column name.
@@ -171,32 +240,49 @@ module Polars
171
240
  Utils.wrap_expr(_rbexpr.meta_undo_aliases)
172
241
  end
173
242
 
174
- # Turn this expression in a selector.
243
+ # Return the original expression.
175
244
  #
176
245
  # @return [Expr]
177
- def _as_selector
178
- Utils.wrap_expr(_rbexpr._meta_as_selector)
246
+ def as_expression
247
+ Utils.wrap_expr(_rbexpr)
179
248
  end
180
249
 
181
- # Add selectors.
250
+ # Try to turn this expression in a selector.
182
251
  #
183
- # @return [Expr]
184
- def _selector_add(other)
185
- Utils.wrap_expr(_rbexpr._meta_selector_add(other._rbexpr))
186
- end
187
-
188
- # Subtract selectors.
252
+ # Raises if the underlying expressions is not a column or selector.
189
253
  #
190
254
  # @return [Expr]
191
- def _selector_sub(other)
192
- Utils.wrap_expr(_rbexpr._meta_selector_sub(other._rbexpr))
255
+ #
256
+ # @note
257
+ # This functionality is considered **unstable**. It may be changed
258
+ # at any point without it being considered a breaking change.
259
+ def as_selector
260
+ Selector._from_rbselector(_rbexpr.into_selector)
193
261
  end
194
262
 
195
- # & selectors.
263
+ # Serialize this expression to a file or string.
196
264
  #
197
- # @return [Expr]
198
- def _selector_and(other)
199
- Utils.wrap_expr(_rbexpr._meta_selector_and(other._rbexpr))
265
+ # @param file [Object]
266
+ # File path to which the result should be written. If set to `nil`
267
+ # (default), the output is returned as a string instead.
268
+ #
269
+ # @return [Object]
270
+ #
271
+ # @note
272
+ # Serialization is not stable across Polars versions: a LazyFrame serialized
273
+ # in one Polars version may not be deserializable in another Polars version.
274
+ #
275
+ # @example Serialize the expression into a binary representation.
276
+ # expr = Polars.col("foo").sum.over("bar")
277
+ # bytes = expr.meta.serialize
278
+ # Polars::Expr.deserialize(StringIO.new(bytes))
279
+ # # => col("foo").sum().over([col("bar")])
280
+ def serialize(file = nil)
281
+ raise Todo unless _rbexpr.respond_to?(:serialize_binary)
282
+
283
+ serializer = _rbexpr.method(:serialize_binary)
284
+
285
+ Utils.serialize_polars_object(serializer, file)
200
286
  end
201
287
 
202
288
  # Format the expression as a tree.
@@ -209,8 +295,8 @@ module Polars
209
295
  # @example
210
296
  # e = (Polars.col("foo") * Polars.col("bar")).sum.over(Polars.col("ham")) / 2
211
297
  # e.meta.tree_format(return_as_string: true)
212
- def tree_format(return_as_string: false)
213
- s = _rbexpr.meta_tree_format
298
+ def tree_format(return_as_string: false, schema: nil)
299
+ s = _rbexpr.meta_tree_format(schema)
214
300
  if return_as_string
215
301
  s
216
302
  else
@@ -218,5 +304,41 @@ module Polars
218
304
  nil
219
305
  end
220
306
  end
307
+
308
+ # Format the expression as a Graphviz graph.
309
+ #
310
+ # Note that Graphviz must be installed to render the visualization (if not
311
+ # already present, you can download it here: https://graphviz.org/download.
312
+ #
313
+ # @param show [Boolean]
314
+ # Show the figure.
315
+ # @param output_path [String]
316
+ # Write the figure to disk.
317
+ # @param raw_output [Boolean]
318
+ # Return dot syntax. This cannot be combined with `show` and/or `output_path`.
319
+ # @param figsize [Array]
320
+ # Passed to matplotlib if `show == true`.
321
+ #
322
+ # @return [Object]
323
+ #
324
+ # @example
325
+ # e = (Polars.col("foo") * Polars.col("bar")).sum.over(Polars.col("ham")) / 2
326
+ # e.meta.show_graph
327
+ def show_graph(
328
+ show: true,
329
+ output_path: nil,
330
+ raw_output: false,
331
+ figsize: [16.0, 12.0],
332
+ schema: nil
333
+ )
334
+ dot = _rbexpr.meta_show_graph(schema)
335
+ Utils.display_dot_graph(
336
+ dot: dot,
337
+ show: show,
338
+ output_path: output_path,
339
+ raw_output: raw_output,
340
+ figsize: figsize
341
+ )
342
+ end
221
343
  end
222
344
  end
@@ -77,8 +77,8 @@ module Polars
77
77
  # # │ 2 ┆ y ┆ 2 ┆ y │
78
78
  # # │ 1 ┆ x ┆ 3 ┆ z │
79
79
  # # └───────────┴───────────┴─────┴─────┘
80
- def map(&f)
81
- Utils.wrap_expr(_rbexpr.name_map(f))
80
+ def map(&function)
81
+ Utils.wrap_expr(_rbexpr.name_map(function))
82
82
  end
83
83
 
84
84
  # Add a prefix to the root column name of the expression.
@@ -194,5 +194,90 @@ module Polars
194
194
  def to_uppercase
195
195
  Utils.wrap_expr(_rbexpr.name_to_uppercase)
196
196
  end
197
+
198
+ # Rename fields of a struct by mapping a function over the field name(s).
199
+ #
200
+ # @note
201
+ # This only takes effect for struct columns.
202
+ #
203
+ # @return [Expr]
204
+ #
205
+ # @example
206
+ # df = Polars::DataFrame.new({"x" => {"a" => 1, "b" => 2}})
207
+ # df.select(Polars.col("x").name.map_fields { |x| x.upcase }).schema
208
+ # # => Polars::Schema({"x"=>Polars::Struct({"A"=>Polars::Int64, "B"=>Polars::Int64})})
209
+ def map_fields(&function)
210
+ Utils.wrap_expr(_rbexpr.name_map_fields(function))
211
+ end
212
+
213
+ # Add a prefix to all field names of a struct.
214
+ #
215
+ # @note
216
+ # This only takes effect for struct columns.
217
+ #
218
+ # @param prefix [String]
219
+ # Prefix to add to the field name.
220
+ #
221
+ # @return [Expr]
222
+ #
223
+ # @example
224
+ # df = Polars::DataFrame.new({"x" => {"a" => 1, "b" => 2}})
225
+ # df.select(Polars.col("x").name.prefix_fields("prefix_")).schema
226
+ # # => Polars::Schema({"x"=>Polars::Struct({"prefix_a"=>Polars::Int64, "prefix_b"=>Polars::Int64})})
227
+ def prefix_fields(prefix)
228
+ Utils.wrap_expr(_rbexpr.name_prefix_fields(prefix))
229
+ end
230
+
231
+ # Replace matching regex/literal substring in the name with a new value.
232
+ #
233
+ # @param pattern [String]
234
+ # A valid regular expression pattern, compatible with the [regex crate](https://docs.rs/regex/latest/regex/).
235
+ # @param value [String]
236
+ # String that will replace the matched substring.
237
+ # @param literal [Boolean]
238
+ # Treat `pattern` as a literal string, not a regex.
239
+ #
240
+ # @return [Expr]
241
+ #
242
+ # @example
243
+ # df = Polars::DataFrame.new(
244
+ # {
245
+ # "n_foo" => [1, 2, 3],
246
+ # "n_bar" => ["x", "y", "z"]
247
+ # }
248
+ # )
249
+ # df.select(Polars.all.name.replace("^n_", "col_"))
250
+ # # =>
251
+ # # shape: (3, 2)
252
+ # # ┌─────────┬─────────┐
253
+ # # │ col_foo ┆ col_bar │
254
+ # # │ --- ┆ --- │
255
+ # # │ i64 ┆ str │
256
+ # # ╞═════════╪═════════╡
257
+ # # │ 1 ┆ x │
258
+ # # │ 2 ┆ y │
259
+ # # │ 3 ┆ z │
260
+ # # └─────────┴─────────┘
261
+ def replace(pattern, value, literal: false)
262
+ Utils.wrap_expr(_rbexpr.name_replace(pattern, value, literal))
263
+ end
264
+
265
+ # Add a suffix to all field names of a struct.
266
+ #
267
+ # @note
268
+ # This only takes effect for struct columns.
269
+ #
270
+ # @param suffix [String]
271
+ # Suffix to add to the field name.
272
+ #
273
+ # @return [Expr]
274
+ #
275
+ # @example
276
+ # df = Polars::DataFrame.new({"x" => {"a" => 1, "b" => 2}})
277
+ # df.select(Polars.col("x").name.suffix_fields("_suffix")).schema
278
+ # # => Polars::Schema({"x"=>Polars::Struct({"a_suffix"=>Polars::Int64, "b_suffix"=>Polars::Int64})})
279
+ def suffix_fields(suffix)
280
+ Utils.wrap_expr(_rbexpr.name_suffix_fields(suffix))
281
+ end
197
282
  end
198
283
  end
@@ -0,0 +1,264 @@
1
+ module Polars
2
+ # The set of the optimizations considered during query optimization.
3
+ #
4
+ # @note
5
+ # This functionality is considered **unstable**. It may be changed
6
+ # at any point without it being considered a breaking change.
7
+ class QueryOptFlags
8
+ # @private
9
+ attr_accessor :_rboptflags
10
+
11
+ def initialize(
12
+ predicate_pushdown: nil,
13
+ projection_pushdown: nil,
14
+ simplify_expression: nil,
15
+ slice_pushdown: nil,
16
+ comm_subplan_elim: nil,
17
+ comm_subexpr_elim: nil,
18
+ cluster_with_columns: nil,
19
+ collapse_joins: nil,
20
+ check_order_observe: nil,
21
+ fast_projection: nil,
22
+ sort_collapse: nil
23
+ )
24
+ self._rboptflags = RbOptFlags.default
25
+ update(
26
+ predicate_pushdown: predicate_pushdown,
27
+ projection_pushdown: projection_pushdown,
28
+ simplify_expression: simplify_expression,
29
+ slice_pushdown: slice_pushdown,
30
+ comm_subplan_elim: comm_subplan_elim,
31
+ comm_subexpr_elim: comm_subexpr_elim,
32
+ cluster_with_columns: cluster_with_columns,
33
+ collapse_joins: collapse_joins,
34
+ check_order_observe: check_order_observe,
35
+ fast_projection: fast_projection,
36
+ sort_collapse: sort_collapse
37
+ )
38
+ end
39
+
40
+ # Create new empty set off optimizations.
41
+ def self.none(
42
+ predicate_pushdown: nil,
43
+ projection_pushdown: nil,
44
+ simplify_expression: nil,
45
+ slice_pushdown: nil,
46
+ comm_subplan_elim: nil,
47
+ comm_subexpr_elim: nil,
48
+ cluster_with_columns: nil,
49
+ collapse_joins: nil,
50
+ check_order_observe: nil,
51
+ fast_projection: nil,
52
+ sort_collapse: nil
53
+ )
54
+ optflags = QueryOptFlags.new
55
+ optflags.no_optimizations
56
+ optflags.update(
57
+ predicate_pushdown: predicate_pushdown,
58
+ projection_pushdown: projection_pushdown,
59
+ simplify_expression: simplify_expression,
60
+ slice_pushdown: slice_pushdown,
61
+ comm_subplan_elim: comm_subplan_elim,
62
+ comm_subexpr_elim: comm_subexpr_elim,
63
+ cluster_with_columns: cluster_with_columns,
64
+ collapse_joins: collapse_joins,
65
+ check_order_observe: check_order_observe,
66
+ fast_projection: fast_projection,
67
+ sort_collapse: sort_collapse
68
+ )
69
+ end
70
+
71
+ def update(
72
+ predicate_pushdown: nil,
73
+ projection_pushdown: nil,
74
+ simplify_expression: nil,
75
+ slice_pushdown: nil,
76
+ comm_subplan_elim: nil,
77
+ comm_subexpr_elim: nil,
78
+ cluster_with_columns: nil,
79
+ collapse_joins: nil,
80
+ check_order_observe: nil,
81
+ fast_projection: nil,
82
+ sort_collapse: nil
83
+ )
84
+ if !predicate_pushdown.nil?
85
+ self.predicate_pushdown = predicate_pushdown
86
+ end
87
+ if !projection_pushdown.nil?
88
+ self.projection_pushdown = projection_pushdown
89
+ end
90
+ if !simplify_expression.nil?
91
+ self.simplify_expression = simplify_expression
92
+ end
93
+ if !slice_pushdown.nil?
94
+ self.slice_pushdown = slice_pushdown
95
+ end
96
+ if !comm_subplan_elim.nil?
97
+ self.comm_subplan_elim = comm_subplan_elim
98
+ end
99
+ if !comm_subexpr_elim.nil?
100
+ self.comm_subexpr_elim = comm_subexpr_elim
101
+ end
102
+ if !cluster_with_columns.nil?
103
+ self.cluster_with_columns = cluster_with_columns
104
+ end
105
+ if !collapse_joins.nil?
106
+ Utils.issue_deprecation_warning(
107
+ "the `collapse_joins` parameter for `QueryOptFlags` is deprecated. " +
108
+ "Use `predicate_pushdown` instead."
109
+ )
110
+ if !collapse_joins
111
+ self.predicate_pushdown = false
112
+ end
113
+ end
114
+ if !check_order_observe.nil?
115
+ self.check_order_observe = check_order_observe
116
+ end
117
+ if !fast_projection.nil?
118
+ self.fast_projection = fast_projection
119
+ end
120
+ if !sort_collapse.nil?
121
+ self.sort_collapse = sort_collapse
122
+ end
123
+
124
+ self
125
+ end
126
+
127
+ # Create new empty set off optimizations.
128
+ def self._eager
129
+ optflags = QueryOptFlags.new
130
+ optflags.no_optimizations
131
+ optflags._rboptflags.eager = true
132
+ optflags.simplify_expression = true
133
+ optflags
134
+ end
135
+
136
+ # Remove selected optimizations.
137
+ def no_optimizations
138
+ _rboptflags.no_optimizations
139
+ end
140
+
141
+ # Only read columns that are used later in the query.
142
+ def projection_pushdown
143
+ _rboptflags.projection_pushdown
144
+ end
145
+
146
+ def projection_pushdown=(value)
147
+ _rboptflags.projection_pushdown = value
148
+ end
149
+
150
+ # Apply predicates/filters as early as possible.
151
+ def predicate_pushdown
152
+ _rboptflags.predicate_pushdown
153
+ end
154
+
155
+ def predicate_pushdown=(value)
156
+ _rboptflags.predicate_pushdown = value
157
+ end
158
+
159
+ # Cluster sequential `with_columns` calls to independent calls.
160
+ def cluster_with_columns
161
+ _rboptflags.cluster_with_columns
162
+ end
163
+
164
+ def cluster_with_columns=(value)
165
+ _rboptflags.cluster_with_columns = value
166
+ end
167
+
168
+ # Run many expression optimization rules until fixed point.
169
+ def simplify_expression
170
+ _rboptflags.simplify_expression
171
+ end
172
+
173
+ def simplify_expression=(value)
174
+ _rboptflags.simplify_expression = value
175
+ end
176
+
177
+ # Pushdown slices/limits.
178
+ def slice_pushdown
179
+ _rboptflags.slice_pushdown
180
+ end
181
+
182
+ def slice_pushdown=(value)
183
+ _rboptflags.slice_pushdown = value
184
+ end
185
+
186
+ # Elide duplicate plans and caches their outputs.
187
+ def comm_subplan_elim
188
+ _rboptflags.comm_subplan_elim
189
+ end
190
+
191
+ def comm_subplan_elim=(value)
192
+ _rboptflags.comm_subplan_elim = value
193
+ end
194
+
195
+ # Elide duplicate expressions and caches their outputs.
196
+ def comm_subexpr_elim
197
+ _rboptflags.comm_subexpr_elim
198
+ end
199
+
200
+ def comm_subexpr_elim=(value)
201
+ _rboptflags.comm_subexpr_elim = value
202
+ end
203
+
204
+ # Do not maintain order if the order would not be observed.
205
+ def check_order_observe
206
+ _rboptflags.check_order_observe
207
+ end
208
+
209
+ def check_order_observe=(value)
210
+ _rboptflags.check_order_observe = value
211
+ end
212
+
213
+ # Replace simple projections with a faster inlined projection that skips the expression engine.
214
+ def fast_projection
215
+ _rboptflags.fast_projection
216
+ end
217
+
218
+ def fast_projection=(value)
219
+ _rboptflags.fast_projection = value
220
+ end
221
+
222
+ # Collapse sequential sort nodes into a single sort node.
223
+ def sort_collapse
224
+ _rboptflags.sort_collapse
225
+ end
226
+
227
+ def sort_collapse=(value)
228
+ _rboptflags.sort_collapse = value
229
+ end
230
+
231
+ def to_s
232
+ <<~STR
233
+ QueryOptFlags {
234
+ type_coercion: #{_rboptflags.type_coercion}
235
+ type_check: #{_rboptflags.type_check}
236
+
237
+ predicate_pushdown: #{predicate_pushdown}
238
+ projection_pushdown: #{projection_pushdown}
239
+ simplify_expression: #{simplify_expression}
240
+ slice_pushdown: #{slice_pushdown}
241
+ comm_subplan_elim: #{comm_subplan_elim}
242
+ comm_subexpr_elim: #{comm_subexpr_elim}
243
+ cluster_with_columns: #{cluster_with_columns}
244
+ check_order_observe: #{check_order_observe}
245
+ fast_projection: #{fast_projection}
246
+
247
+ eager: #{_rboptflags.eager}
248
+ streaming: #{_rboptflags.streaming}
249
+ }
250
+ STR
251
+ end
252
+ alias_method :inspect, :to_s
253
+
254
+ private
255
+
256
+ def initialize_copy(other)
257
+ super
258
+ self._rboptflags = _rboptflags.copy
259
+ end
260
+ end
261
+
262
+ # @private
263
+ DEFAULT_QUERY_OPT_FLAGS = QueryOptFlags.new
264
+ end
@@ -4,13 +4,15 @@ module Polars
4
4
  # This has an `.agg` method which will allow you to run all polars expressions in a
5
5
  # group by context.
6
6
  class RollingGroupBy
7
+ # @private
7
8
  def initialize(
8
9
  df,
9
10
  index_column,
10
11
  period,
11
12
  offset,
12
13
  closed,
13
- group_by
14
+ group_by,
15
+ predicates
14
16
  )
15
17
  period = Utils.parse_as_duration_string(period)
16
18
  offset = Utils.parse_as_duration_string(offset)
@@ -21,15 +23,98 @@ module Polars
21
23
  @offset = offset
22
24
  @closed = closed
23
25
  @group_by = group_by
26
+ @predicates = predicates
24
27
  end
25
28
 
29
+ # Filter groups with a list of predicates after aggregation.
30
+ #
31
+ # Using this method is equivalent to adding the predicates to the aggregation and
32
+ # filtering afterwards.
33
+ #
34
+ # This method can be chained and all conditions will be combined using `&`.
35
+ #
36
+ # @param predicates [Array]
37
+ # Expressions that evaluate to a boolean value for each group. Typically, this
38
+ # requires the use of an aggregation function. Multiple predicates are
39
+ # combined using `&`.
40
+ #
41
+ # @return [RollingGroupBy]
42
+ def having(*predicates)
43
+ RollingGroupBy.new(
44
+ @df,
45
+ @time_column,
46
+ @period,
47
+ @offset,
48
+ @closed,
49
+ @group_by,
50
+ Utils._chain_predicates(@predicates, predicates)
51
+ )
52
+ end
53
+
54
+ # Compute aggregations for each group of a group by operation.
55
+ #
56
+ # @param aggs [Array]
57
+ # Aggregations to compute for each group of the group by operation,
58
+ # specified as positional arguments.
59
+ # Accepts expression input. Strings are parsed as column names.
60
+ # @param named_aggs [Hash]
61
+ # Additional aggregations, specified as keyword arguments.
62
+ # The resulting columns will be renamed to the keyword used.
63
+ #
64
+ # @return [DataFrame]
26
65
  def agg(*aggs, **named_aggs)
66
+ group_by =
67
+ @df.lazy.rolling(
68
+ index_column: @time_column, period: @period, offset: @offset, closed: @closed, group_by: @group_by
69
+ )
70
+
71
+ if @predicates&.any?
72
+ group_by = group_by.having(@predicates)
73
+ end
74
+
75
+ group_by.agg(*aggs, **named_aggs).collect(
76
+ optimizations: QueryOptFlags.none
77
+ )
78
+ end
79
+
80
+ # Apply a custom/user-defined function (UDF) over the groups as a new DataFrame.
81
+ #
82
+ # Using this is considered an anti-pattern as it will be very slow because:
83
+ #
84
+ # - it forces the engine to materialize the whole `DataFrames` for the groups.
85
+ # - it is not parallelized.
86
+ # - it blocks optimizations as the passed python function is opaque to the
87
+ # optimizer.
88
+ #
89
+ # The idiomatic way to apply custom functions over multiple columns is using:
90
+ #
91
+ # `Polars.struct([my_columns]).map_elements { |struct_series| ... }`
92
+ #
93
+ # @param schema [Object]
94
+ # Schema of the output function. This has to be known statically. If the
95
+ # given schema is incorrect, this is a bug in the caller's query and may
96
+ # lead to errors. If set to None, polars assumes the schema is unchanged.
97
+ #
98
+ # @return [DataFrame]
99
+ def map_groups(
100
+ schema,
101
+ &function
102
+ )
103
+ if @predicates&.any?
104
+ msg = "cannot call `map_groups` when filtering groups with `having`"
105
+ raise TypeError, msg
106
+ end
107
+
27
108
  @df.lazy
28
- .group_by_rolling(
29
- index_column: @time_column, period: @period, offset: @offset, closed: @closed, by: @group_by
109
+ .rolling(
110
+ index_column: @time_column,
111
+ period: @period,
112
+ offset: @offset,
113
+ closed: @closed,
114
+ group_by: @group_by
30
115
  )
31
- .agg(*aggs, **named_aggs)
32
- .collect(no_optimization: true, string_cache: false)
116
+ .map_groups(schema, &function)
117
+ .collect(optimizations: QueryOptFlags.none)
33
118
  end
34
119
  end
35
120
  end