polars-df 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/Cargo.lock +90 -45
  4. data/README.md +1 -0
  5. data/ext/polars/Cargo.toml +8 -6
  6. data/ext/polars/src/batched_csv.rs +3 -1
  7. data/ext/polars/src/conversion/anyvalue.rs +3 -2
  8. data/ext/polars/src/conversion/mod.rs +18 -7
  9. data/ext/polars/src/dataframe.rs +40 -14
  10. data/ext/polars/src/expr/array.rs +6 -2
  11. data/ext/polars/src/expr/datetime.rs +7 -2
  12. data/ext/polars/src/expr/general.rs +22 -3
  13. data/ext/polars/src/expr/list.rs +6 -2
  14. data/ext/polars/src/expr/string.rs +3 -3
  15. data/ext/polars/src/file.rs +158 -11
  16. data/ext/polars/src/functions/lazy.rs +18 -3
  17. data/ext/polars/src/functions/whenthen.rs +47 -17
  18. data/ext/polars/src/lazyframe/mod.rs +58 -19
  19. data/ext/polars/src/lib.rs +23 -14
  20. data/ext/polars/src/map/dataframe.rs +17 -9
  21. data/ext/polars/src/series/mod.rs +12 -2
  22. data/lib/polars/array_expr.rb +6 -2
  23. data/lib/polars/batched_csv_reader.rb +4 -2
  24. data/lib/polars/data_frame.rb +148 -74
  25. data/lib/polars/date_time_expr.rb +10 -4
  26. data/lib/polars/date_time_name_space.rb +9 -3
  27. data/lib/polars/expr.rb +37 -34
  28. data/lib/polars/functions/lazy.rb +3 -3
  29. data/lib/polars/functions/whenthen.rb +74 -5
  30. data/lib/polars/io.rb +18 -6
  31. data/lib/polars/lazy_frame.rb +39 -36
  32. data/lib/polars/list_expr.rb +6 -2
  33. data/lib/polars/series.rb +12 -10
  34. data/lib/polars/string_expr.rb +1 -0
  35. data/lib/polars/utils.rb +54 -0
  36. data/lib/polars/version.rb +1 -1
  37. data/lib/polars/whenthen.rb +83 -0
  38. data/lib/polars.rb +1 -2
  39. metadata +4 -5
  40. data/lib/polars/when.rb +0 -16
  41. data/lib/polars/when_then.rb +0 -19
@@ -265,9 +265,9 @@ pub fn apply_lambda_with_rows_output<'a>(
265
265
  // to the row. Before we mutate the row buf again, the reference is dropped.
266
266
  // we only cannot prove it to the compiler.
267
267
  // we still do this because it saves a Vec allocation in a hot loop.
268
- unsafe { &*ptr }
268
+ Ok(unsafe { &*ptr })
269
269
  }
270
- None => &null_row,
270
+ None => Ok(&null_row),
271
271
  }
272
272
  }
273
273
  Err(e) => panic!("ruby function failed {}", e),
@@ -277,22 +277,30 @@ pub fn apply_lambda_with_rows_output<'a>(
277
277
  // first rows for schema inference
278
278
  let mut buf = Vec::with_capacity(inference_size);
279
279
  buf.push(first_value);
280
- buf.extend((&mut row_iter).take(inference_size).cloned());
281
- let schema = rows_to_schema_first_non_null(&buf, Some(50));
280
+ for v in (&mut row_iter).take(inference_size) {
281
+ buf.push(v?.clone());
282
+ }
283
+
284
+ let schema = rows_to_schema_first_non_null(&buf, Some(50))?;
282
285
 
283
286
  if init_null_count > 0 {
284
287
  // Safety: we know the iterators size
285
288
  let iter = unsafe {
286
289
  (0..init_null_count)
287
- .map(|_| &null_row)
288
- .chain(buf.iter())
290
+ .map(|_| Ok(&null_row))
291
+ .chain(buf.iter().map(Ok))
289
292
  .chain(row_iter)
290
293
  .trust_my_length(df.height())
291
294
  };
292
- DataFrame::from_rows_iter_and_schema(iter, &schema)
295
+ DataFrame::try_from_rows_iter_and_schema(iter, &schema)
293
296
  } else {
294
297
  // Safety: we know the iterators size
295
- let iter = unsafe { buf.iter().chain(row_iter).trust_my_length(df.height()) };
296
- DataFrame::from_rows_iter_and_schema(iter, &schema)
298
+ let iter = unsafe {
299
+ buf.iter()
300
+ .map(Ok)
301
+ .chain(row_iter)
302
+ .trust_my_length(df.height())
303
+ };
304
+ DataFrame::try_from_rows_iter_and_schema(iter, &schema)
297
305
  }
298
306
  }
@@ -233,8 +233,18 @@ impl RbSeries {
233
233
  }
234
234
  }
235
235
 
236
- pub fn sort(&self, descending: bool, nulls_last: bool) -> Self {
237
- (self.series.borrow_mut().sort(descending, nulls_last)).into()
236
+ pub fn sort(&self, descending: bool, nulls_last: bool, multithreaded: bool) -> RbResult<Self> {
237
+ Ok(self
238
+ .series
239
+ .borrow_mut()
240
+ .sort(
241
+ SortOptions::default()
242
+ .with_order_descending(descending)
243
+ .with_nulls_last(nulls_last)
244
+ .with_multithreaded(multithreaded),
245
+ )
246
+ .map_err(RbPolarsErr::from)?
247
+ .into())
238
248
  }
239
249
 
240
250
  pub fn value_counts(&self, sorted: bool) -> RbResult<RbDataFrame> {
@@ -333,6 +333,10 @@ module Polars
333
333
  #
334
334
  # @param index [Integer]
335
335
  # Index to return per sub-array
336
+ # @param null_on_oob [Boolean]
337
+ # Behavior if an index is out of bounds:
338
+ # true -> set as null
339
+ # false -> raise an error
336
340
  #
337
341
  # @return [Expr]
338
342
  #
@@ -353,9 +357,9 @@ module Polars
353
357
  # # │ [4, 5, 6] ┆ -2 ┆ 5 │
354
358
  # # │ [7, 8, 9] ┆ 4 ┆ null │
355
359
  # # └───────────────┴─────┴──────┘
356
- def get(index)
360
+ def get(index, null_on_oob: true)
357
361
  index = Utils.parse_as_expression(index)
358
- Utils.wrap_expr(_rbexpr.arr_get(index))
362
+ Utils.wrap_expr(_rbexpr.arr_get(index, null_on_oob))
359
363
  end
360
364
 
361
365
  # Get the first value of the sub-arrays.
@@ -27,7 +27,8 @@ module Polars
27
27
  row_count_offset: 0,
28
28
  sample_size: 1024,
29
29
  eol_char: "\n",
30
- new_columns: nil
30
+ new_columns: nil,
31
+ truncate_ragged_lines: false
31
32
  )
32
33
  if Utils.pathlike?(file)
33
34
  path = Utils.normalise_filepath(file)
@@ -75,7 +76,8 @@ module Polars
75
76
  skip_rows_after_header,
76
77
  Utils._prepare_row_count_args(row_count_name, row_count_offset),
77
78
  sample_size,
78
- eol_char
79
+ eol_char,
80
+ truncate_ragged_lines
79
81
  )
80
82
  self.new_columns = new_columns
81
83
  end
@@ -91,7 +91,8 @@ module Polars
91
91
  row_count_name: nil,
92
92
  row_count_offset: 0,
93
93
  sample_size: 1024,
94
- eol_char: "\n"
94
+ eol_char: "\n",
95
+ truncate_ragged_lines: false
95
96
  )
96
97
  if Utils.pathlike?(file)
97
98
  path = Utils.normalise_filepath(file)
@@ -147,7 +148,8 @@ module Polars
147
148
  skip_rows_after_header: skip_rows_after_header,
148
149
  row_count_name: row_count_name,
149
150
  row_count_offset: row_count_offset,
150
- eol_char: eol_char
151
+ eol_char: eol_char,
152
+ truncate_ragged_lines: truncate_ragged_lines
151
153
  )
152
154
  if columns.nil?
153
155
  return _from_rbdf(scan.collect._df)
@@ -186,7 +188,8 @@ module Polars
186
188
  skip_rows_after_header,
187
189
  Utils._prepare_row_count_args(row_count_name, row_count_offset),
188
190
  sample_size,
189
- eol_char
191
+ eol_char,
192
+ truncate_ragged_lines
190
193
  )
191
194
  )
192
195
  end
@@ -814,8 +817,6 @@ module Polars
814
817
 
815
818
  # Serialize to JSON representation.
816
819
  #
817
- # @return [nil]
818
- #
819
820
  # @param file [String]
820
821
  # File path to which the result should be written.
821
822
  # @param pretty [Boolean]
@@ -823,17 +824,45 @@ module Polars
823
824
  # @param row_oriented [Boolean]
824
825
  # Write to row oriented json. This is slower, but more common.
825
826
  #
826
- # @see #write_ndjson
827
+ # @return [nil]
828
+ #
829
+ # @example
830
+ # df = Polars::DataFrame.new(
831
+ # {
832
+ # "foo" => [1, 2, 3],
833
+ # "bar" => [6, 7, 8]
834
+ # }
835
+ # )
836
+ # df.write_json
837
+ # # => "{\"columns\":[{\"name\":\"foo\",\"datatype\":\"Int64\",\"bit_settings\":\"\",\"values\":[1,2,3]},{\"name\":\"bar\",\"datatype\":\"Int64\",\"bit_settings\":\"\",\"values\":[6,7,8]}]}"
838
+ #
839
+ # @example
840
+ # df.write_json(row_oriented: true)
841
+ # # => "[{\"foo\":1,\"bar\":6},{\"foo\":2,\"bar\":7},{\"foo\":3,\"bar\":8}]"
827
842
  def write_json(
828
- file,
843
+ file = nil,
829
844
  pretty: false,
830
845
  row_oriented: false
831
846
  )
832
847
  if Utils.pathlike?(file)
833
848
  file = Utils.normalise_filepath(file)
834
849
  end
835
-
836
- _df.write_json(file, pretty, row_oriented)
850
+ to_string_io = !file.nil? && file.is_a?(StringIO)
851
+ if file.nil? || to_string_io
852
+ buf = StringIO.new
853
+ buf.set_encoding(Encoding::BINARY)
854
+ _df.write_json(buf, pretty, row_oriented)
855
+ json_bytes = buf.string
856
+
857
+ json_str = json_bytes.force_encoding(Encoding::UTF_8)
858
+ if to_string_io
859
+ file.write(json_str)
860
+ else
861
+ return json_str
862
+ end
863
+ else
864
+ _df.write_json(file, pretty, row_oriented)
865
+ end
837
866
  nil
838
867
  end
839
868
 
@@ -843,12 +872,36 @@ module Polars
843
872
  # File path to which the result should be written.
844
873
  #
845
874
  # @return [nil]
846
- def write_ndjson(file)
875
+ #
876
+ # @example
877
+ # df = Polars::DataFrame.new(
878
+ # {
879
+ # "foo" => [1, 2, 3],
880
+ # "bar" => [6, 7, 8]
881
+ # }
882
+ # )
883
+ # df.write_ndjson()
884
+ # # => "{\"foo\":1,\"bar\":6}\n{\"foo\":2,\"bar\":7}\n{\"foo\":3,\"bar\":8}\n"
885
+ def write_ndjson(file = nil)
847
886
  if Utils.pathlike?(file)
848
887
  file = Utils.normalise_filepath(file)
849
888
  end
850
-
851
- _df.write_ndjson(file)
889
+ to_string_io = !file.nil? && file.is_a?(StringIO)
890
+ if file.nil? || to_string_io
891
+ buf = StringIO.new
892
+ buf.set_encoding(Encoding::BINARY)
893
+ _df.write_ndjson(buf)
894
+ json_bytes = buf.string
895
+
896
+ json_str = json_bytes.force_encoding(Encoding::UTF_8)
897
+ if to_string_io
898
+ file.write(json_str)
899
+ else
900
+ return json_str
901
+ end
902
+ else
903
+ _df.write_ndjson(file)
904
+ end
852
905
  nil
853
906
  end
854
907
 
@@ -1010,7 +1063,7 @@ module Polars
1010
1063
 
1011
1064
  # Write to Apache Parquet file.
1012
1065
  #
1013
- # @param file [String]
1066
+ # @param file [String, Pathname, StringIO]
1014
1067
  # File path to which the file should be written.
1015
1068
  # @param compression ["lz4", "uncompressed", "snappy", "gzip", "lzo", "brotli", "zstd"]
1016
1069
  # Choose "zstd" for good compression performance.
@@ -1027,10 +1080,9 @@ module Polars
1027
1080
  # @param statistics [Boolean]
1028
1081
  # Write statistics to the parquet headers. This requires extra compute.
1029
1082
  # @param row_group_size [Integer, nil]
1030
- # Size of the row groups in number of rows.
1031
- # If `nil` (default), the chunks of the DataFrame are
1032
- # used. Writing in smaller chunks may reduce memory pressure and improve
1033
- # writing speeds.
1083
+ # Size of the row groups in number of rows. Defaults to 512^2 rows.
1084
+ # @param data_page_size [Integer, nil]
1085
+ # Size of the data page in bytes. Defaults to 1024^2 bytes.
1034
1086
  #
1035
1087
  # @return [nil]
1036
1088
  def write_parquet(
@@ -1038,7 +1090,8 @@ module Polars
1038
1090
  compression: "zstd",
1039
1091
  compression_level: nil,
1040
1092
  statistics: false,
1041
- row_group_size: nil
1093
+ row_group_size: nil,
1094
+ data_page_size: nil
1042
1095
  )
1043
1096
  if compression.nil?
1044
1097
  compression = "uncompressed"
@@ -1048,7 +1101,7 @@ module Polars
1048
1101
  end
1049
1102
 
1050
1103
  _df.write_parquet(
1051
- file, compression, compression_level, statistics, row_group_size
1104
+ file, compression, compression_level, statistics, row_group_size, data_page_size
1052
1105
  )
1053
1106
  end
1054
1107
 
@@ -1084,7 +1137,7 @@ module Polars
1084
1137
  # df.estimated_size
1085
1138
  # # => 25888898
1086
1139
  # df.estimated_size("mb")
1087
- # # => 26.702880859375
1140
+ # # => 17.0601749420166
1088
1141
  def estimated_size(unit = "b")
1089
1142
  sz = _df.estimated_size
1090
1143
  Utils.scale_bytes(sz, to: unit)
@@ -2161,12 +2214,13 @@ module Polars
2161
2214
  # closed: "right"
2162
2215
  # ).agg(Polars.col("A").alias("A_agg_list"))
2163
2216
  # # =>
2164
- # # shape: (3, 4)
2217
+ # # shape: (4, 4)
2165
2218
  # # ┌─────────────────┬─────────────────┬─────┬─────────────────┐
2166
2219
  # # │ _lower_boundary ┆ _upper_boundary ┆ idx ┆ A_agg_list │
2167
2220
  # # │ --- ┆ --- ┆ --- ┆ --- │
2168
2221
  # # │ i64 ┆ i64 ┆ i64 ┆ list[str] │
2169
2222
  # # ╞═════════════════╪═════════════════╪═════╪═════════════════╡
2223
+ # # │ -2 ┆ 1 ┆ -2 ┆ ["A", "A"] │
2170
2224
  # # │ 0 ┆ 3 ┆ 0 ┆ ["A", "B", "B"] │
2171
2225
  # # │ 2 ┆ 5 ┆ 2 ┆ ["B", "B", "C"] │
2172
2226
  # # │ 4 ┆ 7 ┆ 4 ┆ ["C"] │
@@ -2621,26 +2675,26 @@ module Polars
2621
2675
  # # ┌─────┬─────┬───────────┐
2622
2676
  # # │ a ┆ b ┆ b_squared │
2623
2677
  # # │ --- ┆ --- ┆ --- │
2624
- # # │ i64 ┆ i64 ┆ f64
2678
+ # # │ i64 ┆ i64 ┆ i64
2625
2679
  # # ╞═════╪═════╪═══════════╡
2626
- # # │ 1 ┆ 2 ┆ 4.0
2627
- # # │ 3 ┆ 4 ┆ 16.0
2628
- # # │ 5 ┆ 6 ┆ 36.0
2680
+ # # │ 1 ┆ 2 ┆ 4
2681
+ # # │ 3 ┆ 4 ┆ 16
2682
+ # # │ 5 ┆ 6 ┆ 36
2629
2683
  # # └─────┴─────┴───────────┘
2630
2684
  #
2631
2685
  # @example Replaced
2632
2686
  # df.with_column(Polars.col("a") ** 2)
2633
2687
  # # =>
2634
2688
  # # shape: (3, 2)
2635
- # # ┌──────┬─────┐
2636
- # # │ a ┆ b │
2637
- # # │ --- ┆ --- │
2638
- # # │ f64 ┆ i64 │
2639
- # # ╞══════╪═════╡
2640
- # # │ 1.0 ┆ 2 │
2641
- # # │ 9.0 ┆ 4 │
2642
- # # │ 25.0 ┆ 6 │
2643
- # # └──────┴─────┘
2689
+ # # ┌─────┬─────┐
2690
+ # # │ a ┆ b │
2691
+ # # │ --- ┆ --- │
2692
+ # # │ i64 ┆ i64 │
2693
+ # # ╞═════╪═════╡
2694
+ # # │ 1 ┆ 2 │
2695
+ # # │ 9 ┆ 4 │
2696
+ # # │ 25 ┆ 6 │
2697
+ # # └─────┴─────┘
2644
2698
  def with_column(column)
2645
2699
  lazy
2646
2700
  .with_column(column)
@@ -2807,16 +2861,36 @@ module Polars
2807
2861
  # # │ 2 ┆ 7.0 │
2808
2862
  # # │ 3 ┆ 8.0 │
2809
2863
  # # └─────┴─────┘
2810
- def drop(columns)
2811
- if columns.is_a?(::Array)
2812
- df = clone
2813
- columns.each do |n|
2814
- df._df.drop_in_place(n)
2815
- end
2816
- df
2817
- else
2818
- _from_rbdf(_df.drop(columns))
2819
- end
2864
+ #
2865
+ # @example Drop multiple columns by passing a list of column names.
2866
+ # df.drop(["bar", "ham"])
2867
+ # # =>
2868
+ # # shape: (3, 1)
2869
+ # # ┌─────┐
2870
+ # # │ foo │
2871
+ # # │ --- │
2872
+ # # │ i64 │
2873
+ # # ╞═════╡
2874
+ # # │ 1 │
2875
+ # # │ 2 │
2876
+ # # │ 3 │
2877
+ # # └─────┘
2878
+ #
2879
+ # @example Use positional arguments to drop multiple columns.
2880
+ # df.drop("foo", "ham")
2881
+ # # =>
2882
+ # # shape: (3, 1)
2883
+ # # ┌─────┐
2884
+ # # │ bar │
2885
+ # # │ --- │
2886
+ # # │ f64 │
2887
+ # # ╞═════╡
2888
+ # # │ 6.0 │
2889
+ # # │ 7.0 │
2890
+ # # │ 8.0 │
2891
+ # # └─────┘
2892
+ def drop(*columns)
2893
+ lazy.drop(*columns).collect(_eager: true)
2820
2894
  end
2821
2895
 
2822
2896
  # Drop in place.
@@ -3735,16 +3809,16 @@ module Polars
3735
3809
  # df.with_columns((Polars.col("a") ** 2).alias("a^2"))
3736
3810
  # # =>
3737
3811
  # # shape: (4, 4)
3738
- # # ┌─────┬──────┬───────┬──────┐
3739
- # # │ a ┆ b ┆ c ┆ a^2
3740
- # # │ --- ┆ --- ┆ --- ┆ ---
3741
- # # │ i64 ┆ f64 ┆ bool ┆ f64
3742
- # # ╞═════╪══════╪═══════╪══════╡
3743
- # # │ 1 ┆ 0.5 ┆ true ┆ 1.0
3744
- # # │ 2 ┆ 4.0 ┆ true ┆ 4.0
3745
- # # │ 3 ┆ 10.0 ┆ false ┆ 9.0
3746
- # # │ 4 ┆ 13.0 ┆ true ┆ 16.0
3747
- # # └─────┴──────┴───────┴──────┘
3812
+ # # ┌─────┬──────┬───────┬─────┐
3813
+ # # │ a ┆ b ┆ c ┆ a^2
3814
+ # # │ --- ┆ --- ┆ --- ┆ ---
3815
+ # # │ i64 ┆ f64 ┆ bool ┆ i64
3816
+ # # ╞═════╪══════╪═══════╪═════╡
3817
+ # # │ 1 ┆ 0.5 ┆ true ┆ 1
3818
+ # # │ 2 ┆ 4.0 ┆ true ┆ 4
3819
+ # # │ 3 ┆ 10.0 ┆ false ┆ 9
3820
+ # # │ 4 ┆ 13.0 ┆ true ┆ 16
3821
+ # # └─────┴──────┴───────┴─────┘
3748
3822
  #
3749
3823
  # @example Added columns will replace existing columns with the same name.
3750
3824
  # df.with_columns(Polars.col("a").cast(Polars::Float64))
@@ -3771,16 +3845,16 @@ module Polars
3771
3845
  # )
3772
3846
  # # =>
3773
3847
  # # shape: (4, 6)
3774
- # # ┌─────┬──────┬───────┬──────┬──────┬───────┐
3775
- # # │ a ┆ b ┆ c ┆ a^2 ┆ b/2 ┆ not c │
3776
- # # │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
3777
- # # │ i64 ┆ f64 ┆ bool ┆ f64 ┆ f64 ┆ bool │
3778
- # # ╞═════╪══════╪═══════╪══════╪══════╪═══════╡
3779
- # # │ 1 ┆ 0.5 ┆ true ┆ 1.0 ┆ 0.25 ┆ false │
3780
- # # │ 2 ┆ 4.0 ┆ true ┆ 4.0 ┆ 2.0 ┆ false │
3781
- # # │ 3 ┆ 10.0 ┆ false ┆ 9.0 ┆ 5.0 ┆ true │
3782
- # # │ 4 ┆ 13.0 ┆ true ┆ 16.0 ┆ 6.5 ┆ false │
3783
- # # └─────┴──────┴───────┴──────┴──────┴───────┘
3848
+ # # ┌─────┬──────┬───────┬─────┬──────┬───────┐
3849
+ # # │ a ┆ b ┆ c ┆ a^2 ┆ b/2 ┆ not c │
3850
+ # # │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
3851
+ # # │ i64 ┆ f64 ┆ bool ┆ i64 ┆ f64 ┆ bool │
3852
+ # # ╞═════╪══════╪═══════╪═════╪══════╪═══════╡
3853
+ # # │ 1 ┆ 0.5 ┆ true ┆ 1 ┆ 0.25 ┆ false │
3854
+ # # │ 2 ┆ 4.0 ┆ true ┆ 4 ┆ 2.0 ┆ false │
3855
+ # # │ 3 ┆ 10.0 ┆ false ┆ 9 ┆ 5.0 ┆ true │
3856
+ # # │ 4 ┆ 13.0 ┆ true ┆ 16 ┆ 6.5 ┆ false │
3857
+ # # └─────┴──────┴───────┴─────┴──────┴───────┘
3784
3858
  #
3785
3859
  # @example Multiple columns also can be added using positional arguments instead of a list.
3786
3860
  # df.with_columns(
@@ -3790,16 +3864,16 @@ module Polars
3790
3864
  # )
3791
3865
  # # =>
3792
3866
  # # shape: (4, 6)
3793
- # # ┌─────┬──────┬───────┬──────┬──────┬───────┐
3794
- # # │ a ┆ b ┆ c ┆ a^2 ┆ b/2 ┆ not c │
3795
- # # │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
3796
- # # │ i64 ┆ f64 ┆ bool ┆ f64 ┆ f64 ┆ bool │
3797
- # # ╞═════╪══════╪═══════╪══════╪══════╪═══════╡
3798
- # # │ 1 ┆ 0.5 ┆ true ┆ 1.0 ┆ 0.25 ┆ false │
3799
- # # │ 2 ┆ 4.0 ┆ true ┆ 4.0 ┆ 2.0 ┆ false │
3800
- # # │ 3 ┆ 10.0 ┆ false ┆ 9.0 ┆ 5.0 ┆ true │
3801
- # # │ 4 ┆ 13.0 ┆ true ┆ 16.0 ┆ 6.5 ┆ false │
3802
- # # └─────┴──────┴───────┴──────┴──────┴───────┘
3867
+ # # ┌─────┬──────┬───────┬─────┬──────┬───────┐
3868
+ # # │ a ┆ b ┆ c ┆ a^2 ┆ b/2 ┆ not c │
3869
+ # # │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
3870
+ # # │ i64 ┆ f64 ┆ bool ┆ i64 ┆ f64 ┆ bool │
3871
+ # # ╞═════╪══════╪═══════╪═════╪══════╪═══════╡
3872
+ # # │ 1 ┆ 0.5 ┆ true ┆ 1 ┆ 0.25 ┆ false │
3873
+ # # │ 2 ┆ 4.0 ┆ true ┆ 4 ┆ 2.0 ┆ false │
3874
+ # # │ 3 ┆ 10.0 ┆ false ┆ 9 ┆ 5.0 ┆ true │
3875
+ # # │ 4 ┆ 13.0 ┆ true ┆ 16 ┆ 6.5 ┆ false │
3876
+ # # └─────┴──────┴───────┴─────┴──────┴───────┘
3803
3877
  #
3804
3878
  # @example Use keyword arguments to easily name your expression inputs.
3805
3879
  # df.with_columns(
@@ -1027,14 +1027,20 @@ module Polars
1027
1027
  # Different from `convert_time_zone`, this will also modify
1028
1028
  # the underlying timestamp,
1029
1029
  #
1030
- # @param tz [String]
1031
- # Time zone for the `Datetime` Series.
1030
+ # @param time_zone [String]
1031
+ # Time zone for the `Datetime` Series. Pass `nil` to unset time zone.
1032
+ # @param use_earliest [Boolean]
1033
+ # Determine how to deal with ambiguous datetimes.
1034
+ # @param ambiguous [String]
1035
+ # Determine how to deal with ambiguous datetimes.
1036
+ # @param non_existent [String]
1037
+ # Determine how to deal with non-existent datetimes.
1032
1038
  #
1033
1039
  # @return [Expr]
1034
- def replace_time_zone(tz, use_earliest: nil, ambiguous: "raise")
1040
+ def replace_time_zone(time_zone, use_earliest: nil, ambiguous: "raise", non_existent: "raise")
1035
1041
  ambiguous = Utils.rename_use_earliest_to_ambiguous(use_earliest, ambiguous)
1036
1042
  ambiguous = Polars.lit(ambiguous) unless ambiguous.is_a?(Expr)
1037
- Utils.wrap_expr(_rbexpr.dt_replace_time_zone(tz, ambiguous._rbexpr))
1043
+ Utils.wrap_expr(_rbexpr.dt_replace_time_zone(time_zone, ambiguous._rbexpr, non_existent))
1038
1044
  end
1039
1045
 
1040
1046
  # Extract the days from a Duration type.
@@ -910,8 +910,14 @@ module Polars
910
910
  # Different from `with_time_zone`, this will also modify
911
911
  # the underlying timestamp.
912
912
  #
913
- # @param tz [String]
914
- # Time zone for the `Datetime` Series.
913
+ # @param time_zone [String]
914
+ # Time zone for the `Datetime` Series. Pass `nil` to unset time zone.
915
+ # @param use_earliest [Boolean]
916
+ # Determine how to deal with ambiguous datetimes.
917
+ # @param ambiguous [String]
918
+ # Determine how to deal with ambiguous datetimes.
919
+ # @param non_existent [String]
920
+ # Determine how to deal with non-existent datetimes.
915
921
  #
916
922
  # @return [Series]
917
923
  #
@@ -982,7 +988,7 @@ module Polars
982
988
  # # 1585717200
983
989
  # # 1588309200
984
990
  # # ]
985
- def replace_time_zone(tz)
991
+ def replace_time_zone(time_zone, use_earliest: nil, ambiguous: "raise", non_existent: "raise")
986
992
  super
987
993
  end
988
994