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
@@ -1,12 +1,39 @@
1
1
  module Polars
2
2
  module Functions
3
+ # Select a field in the current `struct.with_fields` scope.
4
+ #
5
+ # @param name [Object]
6
+ # Name of the field(s) to select.
7
+ #
8
+ # @return [Expr]
9
+ #
10
+ # @example
11
+ # df = Polars::DataFrame.new({"a" => [{"x" => 5, "y" => 2}, {"x" => 3, "y" => 4}]})
12
+ # df.select(Polars.col("a").struct.with_fields(Polars.field("x") ** 2))
13
+ # # =>
14
+ # # shape: (2, 1)
15
+ # # ┌───────────┐
16
+ # # │ a │
17
+ # # │ --- │
18
+ # # │ struct[2] │
19
+ # # ╞═══════════╡
20
+ # # │ {25,2} │
21
+ # # │ {9,4} │
22
+ # # └───────────┘
23
+ def field(name)
24
+ if name.is_a?(::String)
25
+ name = [name]
26
+ end
27
+ Utils.wrap_expr(Plr.field(name))
28
+ end
29
+
3
30
  # Alias for an element in evaluated in an `eval` expression.
4
31
  #
5
32
  # @return [Expr]
6
33
  #
7
34
  # @example A horizontal rank computation by taking the elements of a list
8
35
  # df = Polars::DataFrame.new({"a" => [1, 8, 3], "b" => [4, 5, 2]})
9
- # df.with_column(
36
+ # df.with_columns(
10
37
  # Polars.concat_list(["a", "b"]).list.eval(Polars.element.rank).alias("rank")
11
38
  # )
12
39
  # # =>
@@ -21,7 +48,7 @@ module Polars
21
48
  # # │ 3 ┆ 2 ┆ [2.0, 1.0] │
22
49
  # # └─────┴─────┴────────────┘
23
50
  def element
24
- col("")
51
+ Utils.wrap_expr(Plr.element)
25
52
  end
26
53
 
27
54
  # Return the number of non-null values in the column.
@@ -70,7 +97,7 @@ module Polars
70
97
  def count(*columns)
71
98
  if columns.empty?
72
99
  warn "`Polars.count` is deprecated. Use `Polars.length` instead."
73
- return Utils.wrap_expr(Plr.len._alias("count"))
100
+ return Utils.wrap_expr(Plr.len.alias("count"))
74
101
  end
75
102
 
76
103
  col(*columns).count
@@ -114,6 +141,9 @@ module Polars
114
141
  #
115
142
  # @param columns [Array]
116
143
  # One or more column names.
144
+ # @param maintain_order [Boolean]
145
+ # Whether to preserve the order of elements in the list. Setting this
146
+ # to `false` can improve performance, especially within `group_by`.
117
147
  #
118
148
  # @return [Expr]
119
149
  #
@@ -147,8 +177,8 @@ module Polars
147
177
  # # ╞═══════════╪═══════════════════════╡
148
178
  # # │ [9, 8, 7] ┆ ["foo", "bar", "foo"] │
149
179
  # # └───────────┴───────────────────────┘
150
- def implode(*columns)
151
- col(*columns).implode
180
+ def implode(*columns, maintain_order: true)
181
+ col(*columns).implode(maintain_order: maintain_order)
152
182
  end
153
183
 
154
184
  # Get the standard deviation.
@@ -272,7 +302,6 @@ module Polars
272
302
  def mean(*columns)
273
303
  col(*columns).mean
274
304
  end
275
- alias_method :avg, :mean
276
305
 
277
306
  # Get the median value.
278
307
  #
@@ -458,7 +487,7 @@ module Polars
458
487
  # # └─────┴─────┘
459
488
  def first(*columns)
460
489
  if columns.empty?
461
- return Utils.wrap_expr(Plr.first)
490
+ return cs.first.as_expr
462
491
  end
463
492
 
464
493
  col(*columns).first
@@ -518,7 +547,7 @@ module Polars
518
547
  # # └─────┴─────┘
519
548
  def last(*columns)
520
549
  if columns.empty?
521
- return Utils.wrap_expr(Plr.last)
550
+ return cs.last.as_expr
522
551
  end
523
552
 
524
553
  col(*columns).last
@@ -565,12 +594,8 @@ module Polars
565
594
  # # │ bar ┆ 8 │
566
595
  # # │ baz ┆ 3 │
567
596
  # # └─────┴─────┘
568
- def nth(*indices)
569
- if indices.length == 1 && indices[0].is_a?(Array)
570
- indices = indices[0]
571
- end
572
-
573
- Utils.wrap_expr(Plr.index_cols(indices))
597
+ def nth(*indices, strict: true)
598
+ cs.by_index(*indices, require_all: strict).as_expr
574
599
  end
575
600
 
576
601
  # Get the first `n` rows.
@@ -675,16 +700,20 @@ module Polars
675
700
  # Column name or Expression.
676
701
  # @param b [Object]
677
702
  # Column name or Expression.
703
+ # @param method ["pearson", "spearman"]
704
+ # Correlation method.
678
705
  # @param ddof [Integer]
679
706
  # "Delta Degrees of Freedom": the divisor used in the calculation is N - ddof,
680
707
  # where N represents the number of elements.
681
708
  # By default ddof is 1.
682
- # @param method ["pearson", "spearman"]
683
- # Correlation method.
684
709
  # @param propagate_nans [Boolean]
685
710
  # If `true` any `NaN` encountered will lead to `NaN` in the output.
686
- # Defaults to `False` where `NaN` are regarded as larger than any finite number
711
+ # Defaults to `false` where `NaN` are regarded as larger than any finite number
687
712
  # and thus lead to the highest rank.
713
+ # @param eager [Boolean]
714
+ # Evaluate immediately and return a `Series`; this requires that at least one
715
+ # of the given arguments is a `Series`. If set to `false` (default), return
716
+ # an expression instead.
688
717
  #
689
718
  # @return [Expr]
690
719
  #
@@ -725,23 +754,63 @@ module Polars
725
754
  # # ╞═════╡
726
755
  # # │ 0.5 │
727
756
  # # └─────┘
757
+ #
758
+ # @example Eager evaluation:
759
+ # s1 = Polars::Series.new("a", [1, 8, 3])
760
+ # s2 = Polars::Series.new("b", [4, 5, 2])
761
+ # Polars.corr(s1, s2, eager: true)
762
+ # # =>
763
+ # # shape: (1,)
764
+ # # Series: 'a' [f64]
765
+ # # [
766
+ # # 0.544705
767
+ # # ]
768
+ #
769
+ # @example
770
+ # Polars.corr(s1, s2, method: "spearman", eager: true)
771
+ # # =>
772
+ # # shape: (1,)
773
+ # # Series: 'a' [f64]
774
+ # # [
775
+ # # 0.5
776
+ # # ]
728
777
  def corr(
729
778
  a,
730
779
  b,
731
780
  method: "pearson",
732
- ddof: 1,
733
- propagate_nans: false
781
+ ddof: nil,
782
+ propagate_nans: false,
783
+ eager: false
734
784
  )
735
- a = Utils.parse_into_expression(a)
736
- b = Utils.parse_into_expression(b)
785
+ if !ddof.nil?
786
+ Utils.issue_deprecation_warning(
787
+ "The `ddof` parameter has no effect. Do not use it."
788
+ )
789
+ end
790
+
791
+ if eager
792
+ if !(a.is_a?(Series) || b.is_a?(Series))
793
+ msg = "expected at least one Series in 'corr' inputs if 'eager: true'"
794
+ raise ArgumentError, msg
795
+ end
737
796
 
738
- if method == "pearson"
739
- Utils.wrap_expr(Plr.pearson_corr(a, b, ddof))
740
- elsif method == "spearman"
741
- Utils.wrap_expr(Plr.spearman_rank_corr(a, b, ddof, propagate_nans))
797
+ frame = Polars::DataFrame.new([a, b].filter_map { |e| e if e.is_a?(Series) })
798
+ exprs = [a, b].map { |e| e.is_a?(Series) ? e.name : e }
799
+ frame.select(
800
+ corr(*exprs, eager: false, method: method, propagate_nans: propagate_nans)
801
+ ).to_series
742
802
  else
743
- msg = "method must be one of {{'pearson', 'spearman'}}, got #{method}"
744
- raise ArgumentError, msg
803
+ a = Utils.parse_into_expression(a)
804
+ b = Utils.parse_into_expression(b)
805
+
806
+ if method == "pearson"
807
+ Utils.wrap_expr(Plr.pearson_corr(a, b))
808
+ elsif method == "spearman"
809
+ Utils.wrap_expr(Plr.spearman_rank_corr(a, b, propagate_nans))
810
+ else
811
+ msg = "method must be one of {{'pearson', 'spearman'}}, got #{method}"
812
+ raise ArgumentError, msg
813
+ end
745
814
  end
746
815
  end
747
816
 
@@ -755,6 +824,10 @@ module Polars
755
824
  # "Delta Degrees of Freedom": the divisor used in the calculation is N - ddof,
756
825
  # where N represents the number of elements.
757
826
  # By default ddof is 1.
827
+ # @param eager [Boolean]
828
+ # Evaluate immediately and return a `Series`; this requires that at least one
829
+ # of the given arguments is a `Series`. If set to `false` (default), return
830
+ # an expression instead.
758
831
  #
759
832
  # @return [Expr]
760
833
  #
@@ -776,33 +849,324 @@ module Polars
776
849
  # # ╞═════╡
777
850
  # # │ 3.0 │
778
851
  # # └─────┘
779
- def cov(a, b, ddof: 1)
780
- a = Utils.parse_into_expression(a)
781
- b = Utils.parse_into_expression(b)
782
- Utils.wrap_expr(Plr.cov(a, b, ddof))
852
+ #
853
+ # @example Eager evaluation:
854
+ # s1 = Polars::Series.new("a", [1, 8, 3])
855
+ # s2 = Polars::Series.new("b", [4, 5, 2])
856
+ # Polars.cov(s1, s2, eager: true)
857
+ # # =>
858
+ # # shape: (1,)
859
+ # # Series: 'a' [f64]
860
+ # # [
861
+ # # 3.0
862
+ # # ]
863
+ def cov(a, b, ddof: 1, eager: false)
864
+ if eager
865
+ if !(a.is_a?(Series) || b.is_a?(Series))
866
+ msg = "expected at least one Series in 'cov' inputs if 'eager: true'"
867
+ raise ArgumentError, msg
868
+ end
869
+
870
+ frame = Polars::DataFrame.new([a, b].filter_map { |e| e if e.is_a?(Series) })
871
+ exprs = [a, b].map { |e| e.is_a?(Series) ? e.name : e }
872
+ frame.select(cov(*exprs, eager: false, ddof: ddof)).to_series
873
+ else
874
+ a_rbexpr = Utils.parse_into_expression(a)
875
+ b_rbexpr = Utils.parse_into_expression(b)
876
+ Utils.wrap_expr(Plr.cov(a_rbexpr, b_rbexpr, ddof))
877
+ end
783
878
  end
784
879
 
785
- # def map
786
- # end
880
+ # Map a custom function over multiple columns/expressions.
881
+ #
882
+ # Produces a single Series result.
883
+ #
884
+ # @note
885
+ # This method is much slower than the native expressions API.
886
+ # Only use it if you cannot implement your logic otherwise.
887
+ #
888
+ # @param exprs [Array]
889
+ # Expression(s) representing the input Series to the function.
890
+ # @param return_dtype [Object]
891
+ # Datatype of the output Series.
892
+ #
893
+ # It is recommended to set this whenever possible. If this is `nil`, it tries
894
+ # to infer the datatype by calling the function with dummy data and looking at
895
+ # the output.
896
+ # @param is_elementwise [Boolean]
897
+ # Set to true if the operations is elementwise for better performance
898
+ # and optimization.
899
+ #
900
+ # An elementwise operations has unit or equal length for all inputs
901
+ # and can be ran sequentially on slices without results being affected.
902
+ # @param returns_scalar [Boolean]
903
+ # If the function returns a scalar, by default it will be wrapped in
904
+ # a list in the output, since the assumption is that the function
905
+ # always returns something Series-like. If you want to keep the
906
+ # result as a scalar, set this argument to True.
907
+ #
908
+ # @return [Expr]
909
+ #
910
+ # @note
911
+ # A UDF passed to `map_batches` must be pure, meaning that it cannot modify
912
+ # or depend on state other than its arguments. We may call the function
913
+ # with arbitrary input data.
914
+ #
915
+ # @example
916
+ # test_func = lambda do |a, b, c|
917
+ # a + b + c
918
+ # end
919
+ # df = Polars::DataFrame.new(
920
+ # {
921
+ # "a" => [1, 2, 3, 4],
922
+ # "b" => [4, 5, 6, 7]
923
+ # }
924
+ # )
925
+ #
926
+ # df.with_columns(
927
+ # (
928
+ # Polars.struct(["a", "b"]).map_batches { |x| test_func.(x.struct.field("a"), x.struct.field("b"), 1) }
929
+ # ).alias("a+b+c")
930
+ # )
931
+ # # =>
932
+ # # shape: (4, 3)
933
+ # # ┌─────┬─────┬───────┐
934
+ # # │ a ┆ b ┆ a+b+c │
935
+ # # │ --- ┆ --- ┆ --- │
936
+ # # │ i64 ┆ i64 ┆ i64 │
937
+ # # ╞═════╪═════╪═══════╡
938
+ # # │ 1 ┆ 4 ┆ 6 │
939
+ # # │ 2 ┆ 5 ┆ 8 │
940
+ # # │ 3 ┆ 6 ┆ 10 │
941
+ # # │ 4 ┆ 7 ┆ 12 │
942
+ # # └─────┴─────┴───────┘
943
+ def map_batches(
944
+ exprs,
945
+ return_dtype: nil,
946
+ is_elementwise: false,
947
+ returns_scalar: false,
948
+ &function
949
+ )
950
+ rbexprs = Utils.parse_into_list_of_expressions(exprs)
951
+
952
+ return_dtype_expr =
953
+ if !return_dtype.nil?
954
+ Utils.parse_into_datatype_expr(return_dtype)._rbdatatype_expr
955
+ else
956
+ nil
957
+ end
787
958
 
788
- # def apply
789
- # end
959
+ Utils.wrap_expr(
960
+ Plr.map_expr(
961
+ rbexprs,
962
+ _map_batches_wrapper(function, returns_scalar: returns_scalar),
963
+ return_dtype_expr,
964
+ is_elementwise,
965
+ returns_scalar
966
+ )
967
+ )
968
+ end
969
+
970
+ # Apply a custom/user-defined function (UDF) in a GroupBy context.
971
+ #
972
+ # @note
973
+ # This method is much slower than the native expressions API.
974
+ # Only use it if you cannot implement your logic otherwise.
975
+ #
976
+ # @param exprs [Object]
977
+ # Expression(s) representing the input Series to the function.
978
+ # @param return_dtype [Object]
979
+ # Datatype of the output Series.
980
+ #
981
+ # It is recommended to set this whenever possible. If this is `nil`, it tries
982
+ # to infer the datatype by calling the function with dummy data and looking at
983
+ # the output.
984
+ # @param is_elementwise [Boolean]
985
+ # Set to true if the operations is elementwise for better performance
986
+ # and optimization.
987
+ #
988
+ # An elementwise operations has unit or equal length for all inputs
989
+ # and can be ran sequentially on slices without results being affected.
990
+ # @param returns_scalar [Boolean]
991
+ # If the function returns a single scalar as output.
992
+ #
993
+ # @return [Expr]
994
+ #
995
+ # @example
996
+ # df = Polars::DataFrame.new(
997
+ # {
998
+ # "group" => [1, 1, 2],
999
+ # "a" => [1, 3, 3],
1000
+ # "b" => [5, 6, 7]
1001
+ # }
1002
+ # )
1003
+ # (
1004
+ # df.group_by("group").agg(
1005
+ # Polars.map_groups(["a", "b"], return_dtype: Polars::Float64) { |list_of_series| list_of_series[0] / list_of_series[0].sum + list_of_series[1] }
1006
+ # .alias("my_custom_aggregation")
1007
+ # )
1008
+ # ).sort("group")
1009
+ # # =>
1010
+ # # shape: (2, 2)
1011
+ # # ┌───────┬───────────────────────┐
1012
+ # # │ group ┆ my_custom_aggregation │
1013
+ # # │ --- ┆ --- │
1014
+ # # │ i64 ┆ list[f64] │
1015
+ # # ╞═══════╪═══════════════════════╡
1016
+ # # │ 1 ┆ [5.25, 6.75] │
1017
+ # # │ 2 ┆ [8.0] │
1018
+ # # └───────┴───────────────────────┘
1019
+ def map_groups(
1020
+ exprs,
1021
+ return_dtype: nil,
1022
+ is_elementwise: false,
1023
+ returns_scalar: false,
1024
+ &function
1025
+ )
1026
+ map_batches(
1027
+ exprs,
1028
+ return_dtype: return_dtype,
1029
+ is_elementwise: is_elementwise,
1030
+ returns_scalar: returns_scalar,
1031
+ &function
1032
+ )
1033
+ end
790
1034
 
791
1035
  # Accumulate over multiple columns horizontally/row wise with a left fold.
792
1036
  #
793
1037
  # @return [Expr]
794
- def fold(acc, f, exprs)
1038
+ #
1039
+ # @example Horizontally sum over all columns and add 1.
1040
+ # df = Polars::DataFrame.new(
1041
+ # {
1042
+ # "a" => [1, 2, 3],
1043
+ # "b" => [3, 4, 5],
1044
+ # "c" => [5, 6, 7]
1045
+ # }
1046
+ # )
1047
+ # df.select(
1048
+ # Polars.fold(Polars.lit(1), Polars.col("*")) { |acc, x| acc + x }.alias("sum")
1049
+ # )
1050
+ # # =>
1051
+ # # shape: (3, 1)
1052
+ # # ┌─────┐
1053
+ # # │ sum │
1054
+ # # │ --- │
1055
+ # # │ i32 │
1056
+ # # ╞═════╡
1057
+ # # │ 10 │
1058
+ # # │ 13 │
1059
+ # # │ 16 │
1060
+ # # └─────┘
1061
+ #
1062
+ # @example You can also apply a condition/predicate on all columns:
1063
+ # df = Polars::DataFrame.new(
1064
+ # {
1065
+ # "a" => [1, 2, 3],
1066
+ # "b" => [0, 1, 2]
1067
+ # }
1068
+ # )
1069
+ # df.filter(
1070
+ # Polars.fold(Polars.lit(true), Polars.col("*") > 1) { |acc, x| acc & x }
1071
+ # )
1072
+ # # =>
1073
+ # # shape: (1, 2)
1074
+ # # ┌─────┬─────┐
1075
+ # # │ a ┆ b │
1076
+ # # │ --- ┆ --- │
1077
+ # # │ i64 ┆ i64 │
1078
+ # # ╞═════╪═════╡
1079
+ # # │ 3 ┆ 2 │
1080
+ # # └─────┴─────┘
1081
+ def fold(
1082
+ acc,
1083
+ exprs,
1084
+ returns_scalar: false,
1085
+ return_dtype: nil,
1086
+ &function
1087
+ )
795
1088
  acc = Utils.parse_into_expression(acc, str_as_lit: true)
796
1089
  if exprs.is_a?(Expr)
797
1090
  exprs = [exprs]
798
1091
  end
799
1092
 
1093
+ rt = nil
1094
+ if !return_dtype.nil?
1095
+ rt = Utils.parse_into_datatype_expr(return_dtype)._rbdatatype_expr
1096
+ end
1097
+
800
1098
  exprs = Utils.parse_into_list_of_expressions(exprs)
801
- Utils.wrap_expr(Plr.fold(acc, f, exprs))
1099
+ Utils.wrap_expr(
1100
+ Plr.fold(
1101
+ acc,
1102
+ _wrap_acc_lambda(function),
1103
+ exprs,
1104
+ returns_scalar,
1105
+ rt
1106
+ )
1107
+ )
802
1108
  end
803
1109
 
804
- # def reduce
805
- # end
1110
+ # Accumulate over multiple columns horizontally/ row wise with a left fold.
1111
+ #
1112
+ # @param exprs [Object]
1113
+ # Expressions to aggregate over. May also be a wildcard expression.
1114
+ # @param returns_scalar [Boolean]
1115
+ # Whether or not `function` applied returns a scalar. This must be set correctly
1116
+ # by the user.
1117
+ # @param return_dtype [Object]
1118
+ # Output datatype.
1119
+ # If not set, the dtype will be inferred based on the dtype of the input
1120
+ # expressions.
1121
+ #
1122
+ # @return [Expr]
1123
+ #
1124
+ # @example Horizontally sum over all columns.
1125
+ # df = Polars::DataFrame.new(
1126
+ # {
1127
+ # "a" => [1, 2, 3],
1128
+ # "b" => [0, 1, 2]
1129
+ # }
1130
+ # )
1131
+ # df.select(
1132
+ # Polars.reduce(Polars.col("*")) { |acc, x| acc + x }.alias("sum")
1133
+ # )
1134
+ # # =>
1135
+ # # shape: (3, 1)
1136
+ # # ┌─────┐
1137
+ # # │ sum │
1138
+ # # │ --- │
1139
+ # # │ i64 │
1140
+ # # ╞═════╡
1141
+ # # │ 1 │
1142
+ # # │ 3 │
1143
+ # # │ 5 │
1144
+ # # └─────┘
1145
+ def reduce(
1146
+ exprs,
1147
+ returns_scalar: false,
1148
+ return_dtype: nil,
1149
+ &function
1150
+ )
1151
+ if exprs.is_a?(Expr)
1152
+ exprs = [exprs]
1153
+ end
1154
+
1155
+ rt = nil
1156
+ if !return_dtype.nil?
1157
+ rt = Utils.parse_into_datatype_expr(return_dtype)._rbdatatype_expr
1158
+ end
1159
+
1160
+ rbexprs = Utils.parse_into_list_of_expressions(exprs)
1161
+ Utils.wrap_expr(
1162
+ Plr.reduce(
1163
+ _wrap_acc_lambda(function),
1164
+ rbexprs,
1165
+ returns_scalar,
1166
+ rt
1167
+ )
1168
+ )
1169
+ end
806
1170
 
807
1171
  # Cumulatively accumulate over multiple columns horizontally/row wise with a left fold.
808
1172
  #
@@ -811,11 +1175,14 @@ module Polars
811
1175
  # @param acc [Object]
812
1176
  # Accumulator Expression. This is the value that will be initialized when the fold
813
1177
  # starts. For a sum this could for instance be lit(0).
814
- # @param f [Object]
815
- # Function to apply over the accumulator and the value.
816
- # Fn(acc, value) -> new_value
817
1178
  # @param exprs [Object]
818
1179
  # Expressions to aggregate over. May also be a wildcard expression.
1180
+ # @param returns_scalar [Boolean]
1181
+ # Whether or not `function` applied returns a scalar. This must be set correctly
1182
+ # by the user.
1183
+ # @param return_dtype [Object]
1184
+ # Output datatype.
1185
+ # If not set, the dtype will be inferred based on the dtype of the accumulator.
819
1186
  # @param include_init [Boolean]
820
1187
  # Include the initial accumulator state as struct field.
821
1188
  #
@@ -823,7 +1190,7 @@ module Polars
823
1190
  #
824
1191
  # @note
825
1192
  # If you simply want the first encountered expression as accumulator,
826
- # consider using `cumreduce`.
1193
+ # consider using `cum_reduce`.
827
1194
  #
828
1195
  # @example
829
1196
  # df = Polars::DataFrame.new(
@@ -834,7 +1201,7 @@ module Polars
834
1201
  # }
835
1202
  # )
836
1203
  # df.with_columns(
837
- # Polars.cum_fold(Polars.lit(1), ->(acc, x) { acc + x }, Polars.all)
1204
+ # Polars.cum_fold(Polars.lit(1), Polars.all) { |acc, x| acc + x }
838
1205
  # )
839
1206
  # # =>
840
1207
  # # shape: (3, 4)
@@ -847,69 +1214,103 @@ module Polars
847
1214
  # # │ 2 ┆ 4 ┆ 6 ┆ {3,7,13} │
848
1215
  # # │ 3 ┆ 5 ┆ 7 ┆ {4,9,16} │
849
1216
  # # └─────┴─────┴─────┴───────────┘
850
- def cum_fold(acc, f, exprs, include_init: false)
1217
+ def cum_fold(
1218
+ acc,
1219
+ exprs,
1220
+ returns_scalar: false,
1221
+ return_dtype: nil,
1222
+ include_init: false,
1223
+ &function
1224
+ )
851
1225
  acc = Utils.parse_into_expression(acc, str_as_lit: true)
852
1226
  if exprs.is_a?(Expr)
853
1227
  exprs = [exprs]
854
1228
  end
855
1229
 
1230
+ rt = nil
1231
+ if !return_dtype.nil?
1232
+ rt = Utils.parse_into_datatype_expr(return_dtype)._rbdatatype_expr
1233
+ end
1234
+
856
1235
  exprs = Utils.parse_into_list_of_expressions(exprs)
857
- Utils.wrap_expr(Plr.cum_fold(acc, f, exprs, include_init)._alias("cum_fold"))
1236
+ Utils.wrap_expr(
1237
+ Plr.cum_fold(
1238
+ acc,
1239
+ _wrap_acc_lambda(function),
1240
+ exprs,
1241
+ returns_scalar,
1242
+ rt,
1243
+ include_init
1244
+ ).alias("cum_fold")
1245
+ )
858
1246
  end
859
- alias_method :cumfold, :cum_fold
860
1247
 
861
- # def cum_reduce
862
- # end
863
-
864
- # Compute two argument arctan in radians.
1248
+ # Cumulatively reduce horizontally across columns with a left fold.
865
1249
  #
866
- # Returns the angle (in radians) in the plane between the
867
- # positive x-axis and the ray from the origin to (x,y).
1250
+ # Every cumulative result is added as a separate field in a Struct column.
868
1251
  #
869
- # @param y [Object]
870
- # Column name or Expression.
871
- # @param x [Object]
872
- # Column name or Expression.
1252
+ # @param exprs [Object]
1253
+ # Expressions to aggregate over. May also be a wildcard expression.
1254
+ # @param returns_scalar [Boolean]
1255
+ # Whether or not `function` applied returns a scalar. This must be set correctly
1256
+ # by the user.
1257
+ # @param return_dtype [Object]
1258
+ # Output datatype.
1259
+ # If not set, the dtype will be inferred based on the dtype of the input
1260
+ # expressions.
873
1261
  #
874
1262
  # @return [Expr]
875
1263
  #
876
1264
  # @example
877
- # twoRootTwo = Math.sqrt(2) / 2
878
1265
  # df = Polars::DataFrame.new(
879
1266
  # {
880
- # "y" => [twoRootTwo, -twoRootTwo, twoRootTwo, -twoRootTwo],
881
- # "x" => [twoRootTwo, twoRootTwo, -twoRootTwo, -twoRootTwo]
1267
+ # "a" => [1, 2, 3],
1268
+ # "b" => [3, 4, 5],
1269
+ # "c" => [5, 6, 7]
882
1270
  # }
883
1271
  # )
884
- # df.select(
885
- # Polars.arctan2d("y", "x").alias("atan2d"), Polars.arctan2("y", "x").alias("atan2")
886
- # )
1272
+ # df.with_columns(Polars.cum_reduce(Polars.all) { |acc, x| acc + x })
887
1273
  # # =>
888
- # # shape: (4, 2)
889
- # # ┌────────┬───────────┐
890
- # # │ atan2datan2
891
- # # │ --- ┆ ---
892
- # # │ f64 f64
893
- # # ╞════════╪═══════════╡
894
- # # │ 45.00.785398
895
- # # │ -45.0 -0.785398
896
- # # │ 135.0 2.356194
897
- # # │ -135.0 ┆ -2.356194 │
898
- # # └────────┴───────────┘
899
- def arctan2(y, x)
900
- if Utils.strlike?(y)
901
- y = col(y)
1274
+ # # shape: (3, 4)
1275
+ # # ┌─────┬─────┬─────┬────────────┐
1276
+ # # │ a ┆ b c ┆ cum_reduce
1277
+ # # │ --- ┆ --- ┆ --- ┆ ---
1278
+ # # │ i64 i64 ┆ i64 ┆ struct[3]
1279
+ # # ╞═════╪═════╪═════╪════════════╡
1280
+ # # │ 13 ┆ 5 ┆ {1,4,9}
1281
+ # # │ 2 4 ┆ 6 ┆ {2,6,12}
1282
+ # # │ 3 5 ┆ 7 ┆ {3,8,15}
1283
+ # # └─────┴─────┴─────┴────────────┘
1284
+ def cum_reduce(
1285
+ exprs,
1286
+ returns_scalar: false,
1287
+ return_dtype: nil,
1288
+ &function
1289
+ )
1290
+ if exprs.is_a?(Expr)
1291
+ exprs = [exprs]
902
1292
  end
903
- if Utils.strlike?(x)
904
- x = col(x)
1293
+
1294
+ rt = nil
1295
+ if !return_dtype.nil?
1296
+ rt = Utils.parse_into_datatype_expr(return_dtype)._rbdatatype_expr
905
1297
  end
906
- Utils.wrap_expr(Plr.arctan2(y._rbexpr, x._rbexpr))
1298
+
1299
+ rbexprs = Utils.parse_into_list_of_expressions(exprs)
1300
+ Utils.wrap_expr(
1301
+ Plr.cum_reduce(
1302
+ _wrap_acc_lambda(function),
1303
+ rbexprs,
1304
+ returns_scalar,
1305
+ rt
1306
+ ).alias("cum_reduce")
1307
+ )
907
1308
  end
908
1309
 
909
- # Compute two argument arctan in degrees.
1310
+ # Compute two argument arctan in radians.
910
1311
  #
911
- # Returns the angle (in degrees) in the plane between the positive x-axis
912
- # and the ray from the origin to (x,y).
1312
+ # Returns the angle (in radians) in the plane between the
1313
+ # positive x-axis and the ray from the origin to (x,y).
913
1314
  #
914
1315
  # @param y [Object]
915
1316
  # Column name or Expression.
@@ -919,47 +1320,44 @@ module Polars
919
1320
  # @return [Expr]
920
1321
  #
921
1322
  # @example
922
- # twoRootTwo = Math.sqrt(2) / 2
1323
+ # c = Math.sqrt(2) / 2
923
1324
  # df = Polars::DataFrame.new(
924
1325
  # {
925
- # "y" => [twoRootTwo, -twoRootTwo, twoRootTwo, -twoRootTwo],
926
- # "x" => [twoRootTwo, twoRootTwo, -twoRootTwo, -twoRootTwo]
1326
+ # "y" => [c, -c, c, -c],
1327
+ # "x" => [c, c, -c, -c]
927
1328
  # }
928
1329
  # )
929
- # df.select(
930
- # Polars.arctan2d("y", "x").alias("atan2d"), Polars.arctan2("y", "x").alias("atan2")
931
- # )
1330
+ # df.with_columns(Polars.arctan2("y", "x").alias("atan2"))
932
1331
  # # =>
933
- # # shape: (4, 2)
934
- # # ┌────────┬───────────┐
935
- # # │ atan2d ┆ atan2 │
936
- # # │ --- ┆ --- │
937
- # # │ f64 ┆ f64 │
938
- # # ╞════════╪═══════════╡
939
- # # │ 45.0 ┆ 0.785398 │
940
- # # │ -45.0 ┆ -0.785398 │
941
- # # │ 135.0 ┆ 2.356194 │
942
- # # │ -135.0 ┆ -2.356194 │
943
- # # └────────┴───────────┘
944
- def arctan2d(y, x)
1332
+ # # shape: (4, 3)
1333
+ # # ┌───────────┬───────────┬───────────┐
1334
+ # # │ y ┆ x ┆ atan2 │
1335
+ # # │ --- ┆ --- ┆ ---
1336
+ # # │ f64 ┆ f64 ┆ f64
1337
+ # # ╞═══════════╪═══════════╪═══════════╡
1338
+ # # │ 0.707107 ┆ 0.707107 ┆ 0.785398 │
1339
+ # # │ -0.707107 ┆ 0.707107 ┆ -0.785398 │
1340
+ # # │ 0.707107-0.707107 ┆ 2.356194 │
1341
+ # # │ -0.707107 ┆ -0.707107 ┆ -2.356194 │
1342
+ # # └───────────┴───────────┴───────────┘
1343
+ def arctan2(y, x)
945
1344
  if Utils.strlike?(y)
946
1345
  y = col(y)
947
1346
  end
948
1347
  if Utils.strlike?(x)
949
1348
  x = col(x)
950
1349
  end
951
- Utils.wrap_expr(Plr.arctan2d(y._rbexpr, x._rbexpr))
1350
+ Utils.wrap_expr(Plr.arctan2(y._rbexpr, x._rbexpr))
952
1351
  end
953
1352
 
954
1353
  # Exclude certain columns from a wildcard/regex selection.
955
1354
  #
956
1355
  # @param columns [Object]
957
- # Column(s) to exclude from selection
958
- # This can be:
959
- #
960
- # - a column name, or multiple column names
961
- # - a regular expression starting with `^` and ending with `$`
962
- # - a dtype or multiple dtypes
1356
+ # The name or datatype of the column(s) to exclude. Accepts regular expression
1357
+ # input. Regular expressions should start with `^` and end with `$`.
1358
+ # @param more_columns [Array]
1359
+ # Additional names or datatypes of columns to exclude, specified as positional
1360
+ # arguments.
963
1361
  #
964
1362
  # @return [Object]
965
1363
  #
@@ -1010,8 +1408,8 @@ module Polars
1010
1408
  # # │ 2.5 │
1011
1409
  # # │ 1.5 │
1012
1410
  # # └──────┘
1013
- def exclude(columns)
1014
- col("*").exclude(columns)
1411
+ def exclude(columns, *more_columns)
1412
+ col("*").exclude(columns, *more_columns)
1015
1413
  end
1016
1414
 
1017
1415
  # Syntactic sugar for `Polars.col("foo").agg_groups`.
@@ -1043,8 +1441,16 @@ module Polars
1043
1441
  #
1044
1442
  # @param exprs [Object]
1045
1443
  # Columns use to determine the ordering.
1046
- # @param reverse [Boolean]
1444
+ # @param more_exprs [Array]
1445
+ # Additional columns to arg sort by, specified as positional arguments.
1446
+ # @param descending [Boolean]
1047
1447
  # Default is ascending.
1448
+ # @param nulls_last [Boolean]
1449
+ # Place null values last.
1450
+ # @param multithreaded [Boolean]
1451
+ # Sort using multiple threads.
1452
+ # @param maintain_order [Boolean]
1453
+ # Whether the order should be maintained if elements are equal.
1048
1454
  #
1049
1455
  # @return [Expr]
1050
1456
  #
@@ -1071,7 +1477,7 @@ module Polars
1071
1477
  # # └─────┘
1072
1478
  #
1073
1479
  # @example Compute the arg sort by multiple columns by either passing a list of columns, or by specifying each column as a positional argument.
1074
- # df.select(Polars.arg_sort_by(["a", "b"], reverse: true))
1480
+ # df.select(Polars.arg_sort_by(["a", "b"], descending: true))
1075
1481
  # # =>
1076
1482
  # # shape: (4, 1)
1077
1483
  # # ┌─────┐
@@ -1102,17 +1508,16 @@ module Polars
1102
1508
  def arg_sort_by(
1103
1509
  exprs,
1104
1510
  *more_exprs,
1105
- reverse: false,
1511
+ descending: false,
1106
1512
  nulls_last: false,
1107
1513
  multithreaded: true,
1108
1514
  maintain_order: false
1109
1515
  )
1110
1516
  exprs = Utils.parse_into_list_of_expressions(exprs, *more_exprs)
1111
- reverse = Utils.extend_bool(reverse, exprs.length, "reverse", "exprs")
1517
+ descending = Utils.extend_bool(descending, exprs.length, "descending", "exprs")
1112
1518
  nulls_last = Utils.extend_bool(nulls_last, exprs.length, "nulls_last", "exprs")
1113
- Utils.wrap_expr(Plr.arg_sort_by(exprs, reverse, nulls_last, multithreaded, maintain_order))
1519
+ Utils.wrap_expr(Plr.arg_sort_by(exprs, descending, nulls_last, multithreaded, maintain_order))
1114
1520
  end
1115
- alias_method :argsort_by, :arg_sort_by
1116
1521
 
1117
1522
  # Collect multiple LazyFrames at the same time.
1118
1523
  #
@@ -1120,62 +1525,45 @@ module Polars
1120
1525
  #
1121
1526
  # @param lazy_frames [Boolean]
1122
1527
  # A list of LazyFrames to collect.
1123
- # @param type_coercion [Boolean]
1124
- # Do type coercion optimization.
1125
- # @param predicate_pushdown [Boolean]
1126
- # Do predicate pushdown optimization.
1127
- # @param projection_pushdown [Boolean]
1128
- # Do projection pushdown optimization.
1129
- # @param simplify_expression [Boolean]
1130
- # Run simplify expressions optimization.
1131
- # @param string_cache [Boolean]
1132
- # This argument is deprecated and will be ignored
1133
- # @param no_optimization [Boolean]
1134
- # Turn off optimizations.
1135
- # @param slice_pushdown [Boolean]
1136
- # Slice pushdown optimization.
1137
- # @param common_subplan_elimination [Boolean]
1138
- # Will try to cache branching subplans that occur on self-joins or unions.
1139
- # @param allow_streaming [Boolean]
1140
- # Run parts of the query in a streaming fashion (this is in an alpha state)
1528
+ # @param optimizations
1529
+ # The optimization passes done during query optimization.
1530
+ #
1531
+ # This has no effect if `lazy` is set to `true`.
1532
+ # @param engine [String]
1533
+ # Select the engine used to process the query, optional.
1534
+ # At the moment, if set to `"auto"` (default), the query is run
1535
+ # using the polars streaming engine. Polars will also
1536
+ # attempt to use the engine set by the `POLARS_ENGINE_AFFINITY`
1537
+ # environment variable. If it cannot run the query using the
1538
+ # selected engine, the query is run using the polars streaming
1539
+ # engine.
1540
+ # @param lazy [Boolean]
1541
+ # Return as LazyFrame that can be collected later.
1542
+ # This is only correct if all inputs sink to disk.
1543
+ #
1544
+ # This functionality is considered **unstable**. It may be changed
1545
+ # at any point without it being considered a breaking change.
1141
1546
  #
1142
1547
  # @return [Array]
1143
1548
  def collect_all(
1144
1549
  lazy_frames,
1145
- type_coercion: true,
1146
- predicate_pushdown: true,
1147
- projection_pushdown: true,
1148
- simplify_expression: true,
1149
- string_cache: false,
1150
- no_optimization: false,
1151
- slice_pushdown: true,
1152
- common_subplan_elimination: true,
1153
- allow_streaming: false
1550
+ optimizations: DEFAULT_QUERY_OPT_FLAGS,
1551
+ engine: "auto",
1552
+ lazy: false
1154
1553
  )
1155
- if no_optimization
1156
- predicate_pushdown = false
1157
- projection_pushdown = false
1158
- slice_pushdown = false
1159
- common_subplan_elimination = false
1160
- end
1554
+ lfs = lazy_frames.map { |lf| lf._ldf }
1161
1555
 
1162
- prepared = []
1163
-
1164
- lazy_frames.each do |lf|
1165
- ldf = lf._ldf.optimization_toggle(
1166
- type_coercion,
1167
- predicate_pushdown,
1168
- projection_pushdown,
1169
- simplify_expression,
1170
- slice_pushdown,
1171
- common_subplan_elimination,
1172
- allow_streaming,
1173
- false
1174
- )
1175
- prepared << ldf
1556
+ if lazy
1557
+ msg = "the `lazy` parameter of `collect_all` is considered unstable."
1558
+ Utils.issue_unstable_warning(msg)
1559
+
1560
+ ldf = Plr.collect_all_lazy(lfs, optimizations._rboptflags)
1561
+ lf = LazyFrame._from_pyldf(ldf)
1562
+ return lf
1176
1563
  end
1177
1564
 
1178
- out = Plr.collect_all(prepared)
1565
+ engine = LazyFrame._select_engine(engine)
1566
+ out = Plr.collect_all(lfs, engine, optimizations._rboptflags)
1179
1567
 
1180
1568
  # wrap the rbdataframes into dataframe
1181
1569
  result = out.map { |rbdf| Utils.wrap_df(rbdf) }
@@ -1183,6 +1571,25 @@ module Polars
1183
1571
  result
1184
1572
  end
1185
1573
 
1574
+ # Explain multiple LazyFrames as if passed to `collect_all`.
1575
+ #
1576
+ # Common Subplan Elimination is applied on the combined plan, meaning
1577
+ # that diverging queries will run only once.
1578
+ #
1579
+ # @param lazy_frames [Array]
1580
+ # A list of LazyFrames to collect.
1581
+ # @param optimizations [Object]
1582
+ # The optimization passes done during query optimization.
1583
+ #
1584
+ # @return [String]
1585
+ def explain_all(
1586
+ lazy_frames,
1587
+ optimizations: DEFAULT_QUERY_OPT_FLAGS
1588
+ )
1589
+ lfs = lazy_frames.map { |lf| lf._ldf }
1590
+ Plr.explain_all(lfs, optimizations._rboptflags)
1591
+ end
1592
+
1186
1593
  # Run polars expressions without a context.
1187
1594
  #
1188
1595
  # This is syntactic sugar for running `df.select` on an empty DataFrame.
@@ -1191,6 +1598,9 @@ module Polars
1191
1598
  # Column(s) to select, specified as positional arguments.
1192
1599
  # Accepts expression input. Strings are parsed as column names,
1193
1600
  # other non-expression inputs are parsed as literals.
1601
+ # @param eager [Boolean]
1602
+ # Evaluate immediately and return a `DataFrame` (default); if set to `false`,
1603
+ # return a `LazyFrame` instead.
1194
1604
  # @param named_exprs [Hash]
1195
1605
  # Additional columns to select, specified as keyword arguments.
1196
1606
  # The columns will be renamed to the keyword used.
@@ -1212,8 +1622,9 @@ module Polars
1212
1622
  # # │ 2 │
1213
1623
  # # │ 1 │
1214
1624
  # # └─────┘
1215
- def select(*exprs, **named_exprs)
1216
- DataFrame.new([]).select(*exprs, **named_exprs)
1625
+ def select(*exprs, eager: true, **named_exprs)
1626
+ empty_frame = eager ? Polars::DataFrame.new : Polars::LazyFrame.new
1627
+ empty_frame.select(*exprs, **named_exprs)
1217
1628
  end
1218
1629
 
1219
1630
  # Return indices where `condition` evaluates `true`.
@@ -1258,6 +1669,10 @@ module Polars
1258
1669
  # names, other non-expression inputs are parsed as literals.
1259
1670
  # @param more_exprs [Hash]
1260
1671
  # Additional columns to coalesce, specified as positional arguments.
1672
+ # @param eager [Boolean]
1673
+ # Evaluate immediately and return a `Series`; this requires that at least one
1674
+ # of the given arguments is a `Series`. If set to `false` (default), return
1675
+ # an expression instead.
1261
1676
  #
1262
1677
  # @return [Expr]
1263
1678
  #
@@ -1297,32 +1712,55 @@ module Polars
1297
1712
  # # │ null ┆ null ┆ 3 ┆ 3.0 │
1298
1713
  # # │ null ┆ null ┆ null ┆ 10.0 │
1299
1714
  # # └──────┴──────┴──────┴──────┘
1300
- def coalesce(exprs, *more_exprs)
1301
- exprs = Utils.parse_into_list_of_expressions(exprs, *more_exprs)
1302
- Utils.wrap_expr(Plr.coalesce(exprs))
1715
+ #
1716
+ # @example
1717
+ # s1 = Polars::Series.new("a", [nil, 2, nil])
1718
+ # s2 = Polars::Series.new("b", [1, nil, 3])
1719
+ # Polars.coalesce(s1, s2, eager: true)
1720
+ # # =>
1721
+ # # shape: (3,)
1722
+ # # Series: 'a' [i64]
1723
+ # # [
1724
+ # # 1
1725
+ # # 2
1726
+ # # 3
1727
+ # # ]
1728
+ def coalesce(exprs, *more_exprs, eager: false)
1729
+ if eager
1730
+ exprs = [exprs] + more_exprs
1731
+ series = exprs.filter_map { |e| e if e.is_a?(Series) }
1732
+ if !series.any?
1733
+ msg = "expected at least one Series in 'coalesce' if 'eager: true'"
1734
+ raise ArgumentError, msg
1735
+ end
1736
+
1737
+ exprs = exprs.map { |e| e.is_a?(Series) ? e.name : e }
1738
+ Polars::DataFrame.new(series).select(coalesce(exprs, eager: false)).to_series
1739
+ else
1740
+ exprs = Utils.parse_into_list_of_expressions(exprs, *more_exprs)
1741
+ Utils.wrap_expr(Plr.coalesce(exprs))
1742
+ end
1303
1743
  end
1304
1744
 
1305
1745
  # Utility function that parses an epoch timestamp (or Unix time) to Polars Date(time).
1306
1746
  #
1307
1747
  # Depending on the `unit` provided, this function will return a different dtype:
1308
- # - unit: "d" returns pl.Date
1309
- # - unit: "s" returns pl.Datetime["us"] (pl.Datetime's default)
1310
- # - unit: "ms" returns pl.Datetime["ms"]
1311
- # - unit: "us" returns pl.Datetime["us"]
1312
- # - unit: "ns" returns pl.Datetime["ns"]
1748
+ # - time_unit: "d" returns pl.Date
1749
+ # - time_unit: "s" returns pl.Datetime["us"] (pl.Datetime's default)
1750
+ # - time_unit: "ms" returns pl.Datetime["ms"]
1751
+ # - time_unit: "us" returns pl.Datetime["us"]
1752
+ # - time_unit: "ns" returns pl.Datetime["ns"]
1313
1753
  #
1314
1754
  # @param column [Object]
1315
1755
  # Series or expression to parse integers to pl.Datetime.
1316
- # @param unit [String]
1756
+ # @param time_unit [String]
1317
1757
  # The unit of the timesteps since epoch time.
1318
- # @param eager [Boolean]
1319
- # If eager evaluation is `true`, a Series is returned instead of an Expr.
1320
1758
  #
1321
1759
  # @return [Object]
1322
1760
  #
1323
1761
  # @example
1324
1762
  # df = Polars::DataFrame.new({"timestamp" => [1666683077, 1666683099]}).lazy
1325
- # df.select(Polars.from_epoch(Polars.col("timestamp"), unit: "s")).collect
1763
+ # df.select(Polars.from_epoch(Polars.col("timestamp"), time_unit: "s")).collect
1326
1764
  # # =>
1327
1765
  # # shape: (2, 1)
1328
1766
  # # ┌─────────────────────┐
@@ -1333,32 +1771,102 @@ module Polars
1333
1771
  # # │ 2022-10-25 07:31:17 │
1334
1772
  # # │ 2022-10-25 07:31:39 │
1335
1773
  # # └─────────────────────┘
1336
- def from_epoch(column, unit: "s", eager: false)
1774
+ def from_epoch(column, time_unit: "s")
1337
1775
  if Utils.strlike?(column)
1338
- column = col(column)
1776
+ column = F.col(column)
1339
1777
  elsif !column.is_a?(Series) && !column.is_a?(Expr)
1340
1778
  column = Series.new(column)
1341
1779
  end
1342
1780
 
1343
- if unit == "d"
1344
- expr = column.cast(Date)
1345
- elsif unit == "s"
1346
- expr = (column.cast(Int64) * 1_000_000).cast(Datetime.new("us"))
1347
- elsif Utils::DTYPE_TEMPORAL_UNITS.include?(unit)
1348
- expr = column.cast(Datetime.new(unit))
1781
+ if time_unit == "d"
1782
+ column.cast(Date)
1783
+ elsif time_unit == "s"
1784
+ (column.cast(Int64) * 1_000_000).cast(Datetime.new("us"))
1785
+ elsif Utils::DTYPE_TEMPORAL_UNITS.include?(time_unit)
1786
+ column.cast(Datetime.new(time_unit))
1349
1787
  else
1350
- raise ArgumentError, "'unit' must be one of {{'ns', 'us', 'ms', 's', 'd'}}, got '#{unit}'."
1788
+ raise ArgumentError, "`time_unit` must be one of {{'ns', 'us', 'ms', 's', 'd'}}, got #{time_unit.inspect}."
1351
1789
  end
1790
+ end
1352
1791
 
1353
- if eager
1354
- if !column.is_a?(Series)
1355
- raise ArgumentError, "expected Series or Array if eager: true, got #{column.class.name}"
1356
- else
1357
- column.to_frame.select(expr).to_series
1358
- end
1359
- else
1360
- expr
1792
+ # Compute the rolling covariance between two columns/ expressions.
1793
+ #
1794
+ # The window at a given row includes the row itself and the
1795
+ # `window_size - 1` elements before it.
1796
+ #
1797
+ # @param a [Object]
1798
+ # Column name or Expression.
1799
+ # @param b [Object]
1800
+ # Column name or Expression.
1801
+ # @param window_size [Integer]
1802
+ # The length of the window.
1803
+ # @param min_samples [Integer]
1804
+ # The number of values in the window that should be non-null before computing
1805
+ # a result. If nil, it will be set equal to window size.
1806
+ # @param ddof [Integer]
1807
+ # Delta degrees of freedom. The divisor used in calculations
1808
+ # is `N - ddof`, where `N` represents the number of elements.
1809
+ #
1810
+ # @return [Expr]
1811
+ def rolling_cov(
1812
+ a,
1813
+ b,
1814
+ window_size:,
1815
+ min_samples: nil,
1816
+ ddof: 1
1817
+ )
1818
+ if min_samples.nil?
1819
+ min_samples = window_size
1820
+ end
1821
+ if Utils.strlike?(a)
1822
+ a = F.col(a)
1823
+ end
1824
+ if Utils.strlike?(b)
1825
+ b = F.col(b)
1826
+ end
1827
+ Utils.wrap_expr(
1828
+ Plr.rolling_cov(a._rbexpr, b._rbexpr, window_size, min_samples, ddof)
1829
+ )
1830
+ end
1831
+
1832
+ # Compute the rolling correlation between two columns/ expressions.
1833
+ #
1834
+ # The window at a given row includes the row itself and the
1835
+ # `window_size - 1` elements before it.
1836
+ #
1837
+ # @param a [Object]
1838
+ # Column name or Expression.
1839
+ # @param b [Object]
1840
+ # Column name or Expression.
1841
+ # @param window_size [Integer]
1842
+ # The length of the window.
1843
+ # @param min_samples [Integer]
1844
+ # The number of values in the window that should be non-null before computing
1845
+ # a result. If nil, it will be set equal to window size.
1846
+ # @param ddof [Integer]
1847
+ # Delta degrees of freedom. The divisor used in calculations
1848
+ # is `N - ddof`, where `N` represents the number of elements.
1849
+ #
1850
+ # @return [Expr]
1851
+ def rolling_corr(
1852
+ a,
1853
+ b,
1854
+ window_size:,
1855
+ min_samples: nil,
1856
+ ddof: 1
1857
+ )
1858
+ if min_samples.nil?
1859
+ min_samples = window_size
1361
1860
  end
1861
+ if Utils.strlike?(a)
1862
+ a = F.col(a)
1863
+ end
1864
+ if Utils.strlike?(b)
1865
+ b = F.col(b)
1866
+ end
1867
+ Utils.wrap_expr(
1868
+ Plr.rolling_corr(a._rbexpr, b._rbexpr, window_size, min_samples, ddof)
1869
+ )
1362
1870
  end
1363
1871
 
1364
1872
  # Parse one or more SQL expressions to polars expression(s).
@@ -1403,5 +1911,115 @@ module Polars
1403
1911
  sql.map { |q| Utils.wrap_expr(Plr.sql_expr(q)) }
1404
1912
  end
1405
1913
  end
1914
+
1915
+ # Generates a sequence of integers.
1916
+ #
1917
+ # The length of the returned sequence will match the context length, and the
1918
+ # datatype will match the one returned by `get_index_dtype()`.
1919
+ #
1920
+ # If you would like to generate sequences with custom offsets / length /
1921
+ # step size / datatypes, it is recommended to use `int_range` instead.
1922
+ #
1923
+ # @note
1924
+ # This functionality is considered **unstable**. It may be changed
1925
+ # at any point without it being considered a breaking change.
1926
+ #
1927
+ # @param name [String]
1928
+ # Name of the returned column.
1929
+ #
1930
+ # @return [Expr]
1931
+ #
1932
+ # @example
1933
+ # df = Polars::DataFrame.new({"x" => ["A", "A", "B", "B", "B"]})
1934
+ # df.with_columns(Polars.row_index, Polars.row_index("another_index"))
1935
+ # # =>
1936
+ # # shape: (5, 3)
1937
+ # # ┌─────┬───────┬───────────────┐
1938
+ # # │ x ┆ index ┆ another_index │
1939
+ # # │ --- ┆ --- ┆ --- │
1940
+ # # │ str ┆ u32 ┆ u32 │
1941
+ # # ╞═════╪═══════╪═══════════════╡
1942
+ # # │ A ┆ 0 ┆ 0 │
1943
+ # # │ A ┆ 1 ┆ 1 │
1944
+ # # │ B ┆ 2 ┆ 2 │
1945
+ # # │ B ┆ 3 ┆ 3 │
1946
+ # # │ B ┆ 4 ┆ 4 │
1947
+ # # └─────┴───────┴───────────────┘
1948
+ #
1949
+ # @example
1950
+ # df.group_by("x").agg(Polars.row_index).sort("x")
1951
+ # # =>
1952
+ # # shape: (2, 2)
1953
+ # # ┌─────┬───────────┐
1954
+ # # │ x ┆ index │
1955
+ # # │ --- ┆ --- │
1956
+ # # │ str ┆ list[u32] │
1957
+ # # ╞═════╪═══════════╡
1958
+ # # │ A ┆ [0, 1] │
1959
+ # # │ B ┆ [0, 1, 2] │
1960
+ # # └─────┴───────────┘
1961
+ #
1962
+ # @example
1963
+ # df.select(Polars.row_index)
1964
+ # # =>
1965
+ # # shape: (5, 1)
1966
+ # # ┌───────┐
1967
+ # # │ index │
1968
+ # # │ --- │
1969
+ # # │ u32 │
1970
+ # # ╞═══════╡
1971
+ # # │ 0 │
1972
+ # # │ 1 │
1973
+ # # │ 2 │
1974
+ # # │ 3 │
1975
+ # # │ 4 │
1976
+ # # └───────┘
1977
+ def row_index(name = "index")
1978
+ # Notes
1979
+ # * Dispatching to `int_range` means that we cannot accept an offset
1980
+ # parameter, as unlike `DataFrame.with_row_index`, `int_range` will simply
1981
+ # truncate instead of raising an error.
1982
+ F.int_range(
1983
+ F.len,
1984
+ dtype: get_index_type
1985
+ ).alias(name)
1986
+ end
1987
+
1988
+ private
1989
+
1990
+ def _wrap_acc_lambda(function)
1991
+ lambda do |t|
1992
+ a, b = t
1993
+ function.(Utils.wrap_s(a), Utils.wrap_s(b))._s
1994
+ end
1995
+ end
1996
+
1997
+ def _map_batches_wrapper(function, returns_scalar:)
1998
+ lambda do |sl, *args, **kwargs|
1999
+ return_dtype = kwargs[:return_dtype]
2000
+ slp = sl.map { |s| Utils.wrap_s(s) }
2001
+
2002
+ rv = nil
2003
+ begin
2004
+ rv = function.(slp, *args, **kwargs)
2005
+ rescue ArgumentError => e
2006
+ if e.message == "wrong number of arguments (given 2, expected 1)"
2007
+ kwargs.delete(:return_dtype)
2008
+ rv = function.(slp, *args, **kwargs)
2009
+ else
2010
+ raise
2011
+ end
2012
+ end
2013
+
2014
+ if rv.is_a?(Series)
2015
+ rv._s
2016
+ elsif returns_scalar
2017
+ Series.new([rv], dtype: return_dtype)._s
2018
+ else
2019
+ msg = "`map` with `returns_scalar: false` must return a Series; found #{rv.inspect}.\n\nIf `returns_scalar` is set to `true`, a returned value can be a scalar value."
2020
+ raise TypeError, msg
2021
+ end
2022
+ end
2023
+ end
1406
2024
  end
1407
2025
  end