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
@@ -9,6 +9,109 @@ module Polars
9
9
  self._rbexpr = expr._rbexpr
10
10
  end
11
11
 
12
+ # Offset by `n` business days.
13
+ #
14
+ # @note
15
+ # This functionality is considered **unstable**. It may be changed
16
+ # at any point without it being considered a breaking change.
17
+ #
18
+ # @param n
19
+ # Number of business days to offset by. Can be a single number of an
20
+ # expression.
21
+ # @param week_mask
22
+ # Which days of the week to count. The default is Monday to Friday.
23
+ # If you wanted to count only Monday to Thursday, you would pass
24
+ # `[true, true, true, true, false, false, false]`.
25
+ # @param holidays [Object]
26
+ # Holidays to exclude from the count.
27
+ # @param roll
28
+ # What to do when the start date lands on a non-business day. Options are:
29
+ #
30
+ # - `'raise'`: raise an error
31
+ # - `'forward'`: move to the next business day
32
+ # - `'backward'`: move to the previous business day
33
+ #
34
+ # @return [Expr]
35
+ #
36
+ # @example
37
+ # df = Polars::DataFrame.new({"start" => [Date.new(2020, 1, 1), Date.new(2020, 1, 2)]})
38
+ # df.with_columns(result: Polars.col("start").dt.add_business_days(5))
39
+ # # =>
40
+ # # shape: (2, 2)
41
+ # # ┌────────────┬────────────┐
42
+ # # │ start ┆ result │
43
+ # # │ --- ┆ --- │
44
+ # # │ date ┆ date │
45
+ # # ╞════════════╪════════════╡
46
+ # # │ 2020-01-01 ┆ 2020-01-08 │
47
+ # # │ 2020-01-02 ┆ 2020-01-09 │
48
+ # # └────────────┴────────────┘
49
+ #
50
+ # @example You can pass a custom weekend - for example, if you only take Sunday off:
51
+ # week_mask = [true, true, true, true, true, true, false]
52
+ # df.with_columns(
53
+ # result: Polars.col("start").dt.add_business_days(5, week_mask: week_mask)
54
+ # )
55
+ # # =>
56
+ # # shape: (2, 2)
57
+ # # ┌────────────┬────────────┐
58
+ # # │ start ┆ result │
59
+ # # │ --- ┆ --- │
60
+ # # │ date ┆ date │
61
+ # # ╞════════════╪════════════╡
62
+ # # │ 2020-01-01 ┆ 2020-01-07 │
63
+ # # │ 2020-01-02 ┆ 2020-01-08 │
64
+ # # └────────────┴────────────┘
65
+ #
66
+ # @example You can also pass a list of holidays:
67
+ # holidays = [Date.new(2020, 1, 3), Date.new(2020, 1, 6)]
68
+ # df.with_columns(
69
+ # result: Polars.col("start").dt.add_business_days(5, holidays: holidays)
70
+ # )
71
+ # # =>
72
+ # # shape: (2, 2)
73
+ # # ┌────────────┬────────────┐
74
+ # # │ start ┆ result │
75
+ # # │ --- ┆ --- │
76
+ # # │ date ┆ date │
77
+ # # ╞════════════╪════════════╡
78
+ # # │ 2020-01-01 ┆ 2020-01-10 │
79
+ # # │ 2020-01-02 ┆ 2020-01-13 │
80
+ # # └────────────┴────────────┘
81
+ #
82
+ # @example Roll all dates forwards to the next business day:
83
+ # df = Polars::DataFrame.new({"start" => [Date.new(2020, 1, 5), Date.new(2020, 1, 6)]})
84
+ # df.with_columns(
85
+ # rolled_forwards: Polars.col("start").dt.add_business_days(0, roll: "forward")
86
+ # )
87
+ # # =>
88
+ # # shape: (2, 2)
89
+ # # ┌────────────┬─────────────────┐
90
+ # # │ start ┆ rolled_forwards │
91
+ # # │ --- ┆ --- │
92
+ # # │ date ┆ date │
93
+ # # ╞════════════╪═════════════════╡
94
+ # # │ 2020-01-05 ┆ 2020-01-06 │
95
+ # # │ 2020-01-06 ┆ 2020-01-06 │
96
+ # # └────────────┴─────────────────┘
97
+ def add_business_days(
98
+ n,
99
+ week_mask: [true, true, true, true, true, false, false],
100
+ holidays: [],
101
+ roll: "raise"
102
+ )
103
+ n_rbexpr = Utils.parse_into_expression(n)
104
+ holidays_rbexpr = Utils._holidays_to_expr(holidays)
105
+ Utils.wrap_expr(
106
+ _rbexpr.dt_add_business_days(
107
+ n_rbexpr,
108
+ week_mask,
109
+ holidays_rbexpr,
110
+ roll
111
+ )
112
+ )
113
+ end
114
+
12
115
  # Divide the date/datetime range into buckets.
13
116
  #
14
117
  # Each date/datetime is mapped to the start of its bucket.
@@ -203,6 +306,93 @@ module Polars
203
306
  Utils.wrap_expr(_rbexpr.dt_round(every))
204
307
  end
205
308
 
309
+ # Replace time unit.
310
+ #
311
+ # @param year [Object]
312
+ # Column or literal.
313
+ # @param month [Object]
314
+ # Column or literal, ranging from 1-12.
315
+ # @param day [Object]
316
+ # Column or literal, ranging from 1-31.
317
+ # @param hour [Object]
318
+ # Column or literal, ranging from 0-23.
319
+ # @param minute [Object]
320
+ # Column or literal, ranging from 0-59.
321
+ # @param second [Object]
322
+ # Column or literal, ranging from 0-59.
323
+ # @param microsecond [Object]
324
+ # Column or literal, ranging from 0-999999.
325
+ # @param ambiguous [String]
326
+ # Determine how to deal with ambiguous datetimes:
327
+ #
328
+ # - `'raise'` (default): raise
329
+ # - `'earliest'`: use the earliest datetime
330
+ # - `'latest'`: use the latest datetime
331
+ # - `'null'`: set to null
332
+ #
333
+ # @return [Expr]
334
+ #
335
+ # @example
336
+ # df = Polars::DataFrame.new(
337
+ # {
338
+ # "date" => [Date.new(2024, 4, 1), Date.new(2025, 3, 16)],
339
+ # "new_day" => [10, 15]
340
+ # }
341
+ # )
342
+ # df.with_columns(Polars.col("date").dt.replace(day: "new_day").alias("replaced"))
343
+ # # =>
344
+ # # shape: (2, 3)
345
+ # # ┌────────────┬─────────┬────────────┐
346
+ # # │ date ┆ new_day ┆ replaced │
347
+ # # │ --- ┆ --- ┆ --- │
348
+ # # │ date ┆ i64 ┆ date │
349
+ # # ╞════════════╪═════════╪════════════╡
350
+ # # │ 2024-04-01 ┆ 10 ┆ 2024-04-10 │
351
+ # # │ 2025-03-16 ┆ 15 ┆ 2025-03-15 │
352
+ # # └────────────┴─────────┴────────────┘
353
+ #
354
+ # @example
355
+ # df.with_columns(Polars.col("date").dt.replace(year: 1800).alias("replaced"))
356
+ # # =>
357
+ # # shape: (2, 3)
358
+ # # ┌────────────┬─────────┬────────────┐
359
+ # # │ date ┆ new_day ┆ replaced │
360
+ # # │ --- ┆ --- ┆ --- │
361
+ # # │ date ┆ i64 ┆ date │
362
+ # # ╞════════════╪═════════╪════════════╡
363
+ # # │ 2024-04-01 ┆ 10 ┆ 1800-04-01 │
364
+ # # │ 2025-03-16 ┆ 15 ┆ 1800-03-16 │
365
+ # # └────────────┴─────────┴────────────┘
366
+ def replace(
367
+ year: nil,
368
+ month: nil,
369
+ day: nil,
370
+ hour: nil,
371
+ minute: nil,
372
+ second: nil,
373
+ microsecond: nil,
374
+ ambiguous: "raise"
375
+ )
376
+ day, month, year, hour, minute, second, microsecond = (
377
+ Utils.parse_into_list_of_expressions(
378
+ day, month, year, hour, minute, second, microsecond
379
+ )
380
+ )
381
+ ambiguous_expr = Utils.parse_into_expression(ambiguous, str_as_lit: true)
382
+ Utils.wrap_expr(
383
+ _rbexpr.dt_replace(
384
+ year,
385
+ month,
386
+ day,
387
+ hour,
388
+ minute,
389
+ second,
390
+ microsecond,
391
+ ambiguous_expr
392
+ )
393
+ )
394
+ end
395
+
206
396
  # Create a naive Datetime from an existing Date/Datetime expression and a Time.
207
397
  #
208
398
  # If the underlying expression is a Datetime then its time component is replaced,
@@ -215,7 +405,7 @@ module Polars
215
405
  #
216
406
  # @return [Expr]
217
407
  def combine(time, time_unit: "us")
218
- unless time.is_a?(Time) || time.is_a?(Expr)
408
+ unless time.is_a?(::Time) || time.is_a?(Expr)
219
409
  raise TypeError, "expected 'time' to be a Ruby time or Polars expression, found #{time}"
220
410
  end
221
411
  time = Utils.parse_into_expression(time)
@@ -260,13 +450,21 @@ module Polars
260
450
  # # │ 2020-04-01 00:00:00 ┆ 2020/04/01 00:00:00 │
261
451
  # # │ 2020-05-01 00:00:00 ┆ 2020/05/01 00:00:00 │
262
452
  # # └─────────────────────┴─────────────────────┘
263
- def to_string(format)
453
+ def to_string(format = nil)
454
+ if format.nil?
455
+ format = "iso"
456
+ end
264
457
  Utils.wrap_expr(_rbexpr.dt_to_string(format))
265
458
  end
266
459
 
267
- # Format Date/datetime with a formatting rule.
460
+ # Convert a Date/Time/Datetime column into a String column with the given format.
461
+ #
462
+ # Similar to `cast(Polars::String)`, but this method allows you to customize the
463
+ # formatting of the resulting string.
268
464
  #
269
- # See [chrono strftime/strptime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).
465
+ # @param format [String]
466
+ # Format to use, refer to the [chrono strftime documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
467
+ # for specification. Example: `"%y-%m-%d"`.
270
468
  #
271
469
  # @return [Expr]
272
470
  #
@@ -313,8 +511,84 @@ module Polars
313
511
  # # │ 2020-04-01 00:00:00 ┆ Wednesday ┆ April │
314
512
  # # │ 2020-05-01 00:00:00 ┆ Friday ┆ May │
315
513
  # # └─────────────────────┴───────────┴────────────┘
316
- def strftime(fmt)
317
- Utils.wrap_expr(_rbexpr.strftime(fmt))
514
+ def strftime(format)
515
+ Utils.wrap_expr(_rbexpr.strftime(format))
516
+ end
517
+
518
+ # Extract the millennium from underlying representation.
519
+ #
520
+ # Applies to Date and Datetime columns.
521
+ #
522
+ # Returns the millennium number in the calendar date.
523
+ #
524
+ # @return [Expr]
525
+ #
526
+ # @example
527
+ # df = Polars::DataFrame.new(
528
+ # {
529
+ # "date" => [
530
+ # Date.new(999, 12, 31),
531
+ # Date.new(1897, 5, 7),
532
+ # Date.new(2000, 1, 1),
533
+ # Date.new(2001, 7, 5),
534
+ # Date.new(3002, 10, 20)
535
+ # ]
536
+ # }
537
+ # )
538
+ # df.with_columns(mlnm: Polars.col("date").dt.millennium)
539
+ # # =>
540
+ # # shape: (5, 2)
541
+ # # ┌────────────┬──────┐
542
+ # # │ date ┆ mlnm │
543
+ # # │ --- ┆ --- │
544
+ # # │ date ┆ i32 │
545
+ # # ╞════════════╪══════╡
546
+ # # │ 0999-12-31 ┆ 1 │
547
+ # # │ 1897-05-07 ┆ 2 │
548
+ # # │ 2000-01-01 ┆ 2 │
549
+ # # │ 2001-07-05 ┆ 3 │
550
+ # # │ 3002-10-20 ┆ 4 │
551
+ # # └────────────┴──────┘
552
+ def millennium
553
+ Utils.wrap_expr(_rbexpr.dt_millennium)
554
+ end
555
+
556
+ # Extract the century from underlying representation.
557
+ #
558
+ # Applies to Date and Datetime columns.
559
+ #
560
+ # Returns the century number in the calendar date.
561
+ #
562
+ # @return [Expr]
563
+ #
564
+ # @example
565
+ # df = Polars::DataFrame.new(
566
+ # {
567
+ # "date" => [
568
+ # Date.new(999, 12, 31),
569
+ # Date.new(1897, 5, 7),
570
+ # Date.new(2000, 1, 1),
571
+ # Date.new(2001, 7, 5),
572
+ # Date.new(3002, 10, 20)
573
+ # ]
574
+ # }
575
+ # )
576
+ # df.with_columns(cent: Polars.col("date").dt.century)
577
+ # # =>
578
+ # # shape: (5, 2)
579
+ # # ┌────────────┬──────┐
580
+ # # │ date ┆ cent │
581
+ # # │ --- ┆ --- │
582
+ # # │ date ┆ i32 │
583
+ # # ╞════════════╪══════╡
584
+ # # │ 0999-12-31 ┆ 10 │
585
+ # # │ 1897-05-07 ┆ 19 │
586
+ # # │ 2000-01-01 ┆ 20 │
587
+ # # │ 2001-07-05 ┆ 21 │
588
+ # # │ 3002-10-20 ┆ 31 │
589
+ # # └────────────┴──────┘
590
+ def century
591
+ Utils.wrap_expr(_rbexpr.dt_century)
318
592
  end
319
593
 
320
594
  # Extract year from underlying Date representation.
@@ -348,6 +622,79 @@ module Polars
348
622
  Utils.wrap_expr(_rbexpr.dt_year)
349
623
  end
350
624
 
625
+ # Determine whether each day lands on a business day.
626
+ #
627
+ # @note
628
+ # This functionality is considered **unstable**. It may be changed
629
+ # at any point without it being considered a breaking change.
630
+ #
631
+ # @param week_mask [Array]
632
+ # Which days of the week to count. The default is Monday to Friday.
633
+ # If you wanted to count only Monday to Thursday, you would pass
634
+ # `[true, true, true, true, false, false, false]`.
635
+ # @param holidays [Object]
636
+ # Holidays to exclude from the count.
637
+ #
638
+ # @return [Expr]
639
+ #
640
+ # @example
641
+ # df = Polars::DataFrame.new({"start" => [Date.new(2020, 1, 3), Date.new(2020, 1, 5)]})
642
+ # df.with_columns(is_business_day: Polars.col("start").dt.is_business_day)
643
+ # # =>
644
+ # # shape: (2, 2)
645
+ # # ┌────────────┬─────────────────┐
646
+ # # │ start ┆ is_business_day │
647
+ # # │ --- ┆ --- │
648
+ # # │ date ┆ bool │
649
+ # # ╞════════════╪═════════════════╡
650
+ # # │ 2020-01-03 ┆ true │
651
+ # # │ 2020-01-05 ┆ false │
652
+ # # └────────────┴─────────────────┘
653
+ #
654
+ # @example You can pass a custom weekend - for example, if you only take Sunday off:
655
+ # week_mask = [true, true, true, true, true, true, false]
656
+ # df.with_columns(
657
+ # is_business_day: Polars.col("start").dt.is_business_day(week_mask: week_mask)
658
+ # )
659
+ # # =>
660
+ # # shape: (2, 2)
661
+ # # ┌────────────┬─────────────────┐
662
+ # # │ start ┆ is_business_day │
663
+ # # │ --- ┆ --- │
664
+ # # │ date ┆ bool │
665
+ # # ╞════════════╪═════════════════╡
666
+ # # │ 2020-01-03 ┆ true │
667
+ # # │ 2020-01-05 ┆ false │
668
+ # # └────────────┴─────────────────┘
669
+ #
670
+ # @example You can also pass a list of holidays:
671
+ # holidays = [Date.new(2020, 1, 3), Date.new(2020, 1, 6)]
672
+ # df.with_columns(
673
+ # is_business_day: Polars.col("start").dt.is_business_day(holidays: holidays)
674
+ # )
675
+ # # =>
676
+ # # shape: (2, 2)
677
+ # # ┌────────────┬─────────────────┐
678
+ # # │ start ┆ is_business_day │
679
+ # # │ --- ┆ --- │
680
+ # # │ date ┆ bool │
681
+ # # ╞════════════╪═════════════════╡
682
+ # # │ 2020-01-03 ┆ false │
683
+ # # │ 2020-01-05 ┆ false │
684
+ # # └────────────┴─────────────────┘
685
+ def is_business_day(
686
+ week_mask: [true, true, true, true, true, false, false],
687
+ holidays: []
688
+ )
689
+ holidays_rbexpr = Utils._holidays_to_expr(holidays)
690
+ Utils.wrap_expr(
691
+ _rbexpr.dt_is_business_day(
692
+ week_mask,
693
+ holidays_rbexpr
694
+ )
695
+ )
696
+ end
697
+
351
698
  # Determine whether the year of the underlying date is a leap year.
352
699
  #
353
700
  # Applies to Date and Datetime columns.
@@ -466,6 +813,35 @@ module Polars
466
813
  Utils.wrap_expr(_rbexpr.dt_month)
467
814
  end
468
815
 
816
+ # Extract the number of days in the month from the underlying Date representation.
817
+ #
818
+ # Applies to Date and Datetime columns.
819
+ #
820
+ # Returns the number of days in the month.
821
+ # The return value ranges from 28 to 31.
822
+ #
823
+ # @return [Expr]
824
+ #
825
+ # @example
826
+ # df = Polars::DataFrame.new(
827
+ # {"date" => [Date.new(2001, 1, 1), Date.new(2001, 2, 1), Date.new(2000, 2, 1)]}
828
+ # )
829
+ # df.with_columns(Polars.col("date").dt.days_in_month.alias("days_in_month"))
830
+ # # =>
831
+ # # shape: (3, 2)
832
+ # # ┌────────────┬───────────────┐
833
+ # # │ date ┆ days_in_month │
834
+ # # │ --- ┆ --- │
835
+ # # │ date ┆ i8 │
836
+ # # ╞════════════╪═══════════════╡
837
+ # # │ 2001-01-01 ┆ 31 │
838
+ # # │ 2001-02-01 ┆ 28 │
839
+ # # │ 2000-02-01 ┆ 29 │
840
+ # # └────────────┴───────────────┘
841
+ def days_in_month
842
+ Utils.wrap_expr(_rbexpr.dt_days_in_month)
843
+ end
844
+
469
845
  # Extract the week from the underlying Date representation.
470
846
  #
471
847
  # Applies to Date and Datetime columns.
@@ -645,13 +1021,6 @@ module Polars
645
1021
  Utils.wrap_expr(_rbexpr.dt_date)
646
1022
  end
647
1023
 
648
- # Datetime
649
- #
650
- # @return [Expr]
651
- def datetime
652
- Utils.wrap_expr(_rbexpr.dt_datetime)
653
- end
654
-
655
1024
  # Extract hour from underlying DateTime representation.
656
1025
  #
657
1026
  # Applies to Datetime columns.
@@ -937,9 +1306,9 @@ module Polars
937
1306
  if Utils::DTYPE_TEMPORAL_UNITS.include?(time_unit)
938
1307
  timestamp(time_unit)
939
1308
  elsif time_unit == "s"
940
- Utils.wrap_expr(_rbexpr.dt_epoch_seconds)
1309
+ timestamp("ms").floordiv(F.lit(1000, dtype: Int64))
941
1310
  elsif time_unit == "d"
942
- Utils.wrap_expr(_rbexpr).cast(:date).cast(:i32)
1311
+ Utils.wrap_expr(_rbexpr).cast(Date).cast(Int32)
943
1312
  else
944
1313
  raise ArgumentError, "time_unit must be one of {'ns', 'us', 'ms', 's', 'd'}, got #{time_unit.inspect}"
945
1314
  end
@@ -977,47 +1346,6 @@ module Polars
977
1346
  Utils.wrap_expr(_rbexpr.dt_timestamp(time_unit))
978
1347
  end
979
1348
 
980
- # Set time unit of a Series of dtype Datetime or Duration.
981
- #
982
- # This does not modify underlying data, and should be used to fix an incorrect
983
- # time unit.
984
- #
985
- # @param time_unit ["ns", "us", "ms"]
986
- # Time unit for the `Datetime` Series.
987
- #
988
- # @return [Expr]
989
- #
990
- # @example
991
- # df = Polars::DataFrame.new(
992
- # {
993
- # "date" => Polars.datetime_range(
994
- # Time.utc(2001, 1, 1),
995
- # Time.utc(2001, 1, 3),
996
- # "1d",
997
- # time_unit: "ns",
998
- # eager: true
999
- # )
1000
- # }
1001
- # )
1002
- # df.select(
1003
- # Polars.col("date"),
1004
- # Polars.col("date").dt.with_time_unit("us").alias("time_unit_us")
1005
- # )
1006
- # # =>
1007
- # # shape: (3, 2)
1008
- # # ┌─────────────────────┬───────────────────────┐
1009
- # # │ date ┆ time_unit_us │
1010
- # # │ --- ┆ --- │
1011
- # # │ datetime[ns] ┆ datetime[μs] │
1012
- # # ╞═════════════════════╪═══════════════════════╡
1013
- # # │ 2001-01-01 00:00:00 ┆ +32971-04-28 00:00:00 │
1014
- # # │ 2001-01-02 00:00:00 ┆ +32974-01-22 00:00:00 │
1015
- # # │ 2001-01-03 00:00:00 ┆ +32976-10-18 00:00:00 │
1016
- # # └─────────────────────┴───────────────────────┘
1017
- def with_time_unit(time_unit)
1018
- Utils.wrap_expr(_rbexpr.dt_with_time_unit(time_unit))
1019
- end
1020
-
1021
1349
  # Cast the underlying data to another time unit. This may lose precision.
1022
1350
  #
1023
1351
  # @param time_unit ["ns", "us", "ms"]
@@ -1184,6 +1512,9 @@ module Polars
1184
1512
 
1185
1513
  # Extract the days from a Duration type.
1186
1514
  #
1515
+ # @param fractional [Boolean]
1516
+ # Whether to include the fractional component of the second.
1517
+ #
1187
1518
  # @return [Expr]
1188
1519
  #
1189
1520
  # @example
@@ -1197,7 +1528,7 @@ module Polars
1197
1528
  # df.select(
1198
1529
  # [
1199
1530
  # Polars.col("date"),
1200
- # Polars.col("date").diff.dt.days.alias("days_diff")
1531
+ # Polars.col("date").diff.dt.total_days.alias("days_diff")
1201
1532
  # ]
1202
1533
  # )
1203
1534
  # # =>
@@ -1211,13 +1542,15 @@ module Polars
1211
1542
  # # │ 2020-04-01 00:00:00 ┆ 31 │
1212
1543
  # # │ 2020-05-01 00:00:00 ┆ 30 │
1213
1544
  # # └─────────────────────┴───────────┘
1214
- def total_days
1215
- Utils.wrap_expr(_rbexpr.dt_total_days)
1545
+ def total_days(fractional: false)
1546
+ Utils.wrap_expr(_rbexpr.dt_total_days(fractional))
1216
1547
  end
1217
- alias_method :days, :total_days
1218
1548
 
1219
1549
  # Extract the hours from a Duration type.
1220
1550
  #
1551
+ # @param fractional [Boolean]
1552
+ # Whether to include the fractional component of the second.
1553
+ #
1221
1554
  # @return [Expr]
1222
1555
  #
1223
1556
  # @example
@@ -1231,7 +1564,7 @@ module Polars
1231
1564
  # df.select(
1232
1565
  # [
1233
1566
  # Polars.col("date"),
1234
- # Polars.col("date").diff.dt.hours.alias("hours_diff")
1567
+ # Polars.col("date").diff.dt.total_hours.alias("hours_diff")
1235
1568
  # ]
1236
1569
  # )
1237
1570
  # # =>
@@ -1246,13 +1579,15 @@ module Polars
1246
1579
  # # │ 2020-01-03 00:00:00 ┆ 24 │
1247
1580
  # # │ 2020-01-04 00:00:00 ┆ 24 │
1248
1581
  # # └─────────────────────┴────────────┘
1249
- def total_hours
1250
- Utils.wrap_expr(_rbexpr.dt_total_hours)
1582
+ def total_hours(fractional: false)
1583
+ Utils.wrap_expr(_rbexpr.dt_total_hours(fractional))
1251
1584
  end
1252
- alias_method :hours, :total_hours
1253
1585
 
1254
1586
  # Extract the minutes from a Duration type.
1255
1587
  #
1588
+ # @param fractional [Boolean]
1589
+ # Whether to include the fractional component of the second.
1590
+ #
1256
1591
  # @return [Expr]
1257
1592
  #
1258
1593
  # @example
@@ -1266,7 +1601,7 @@ module Polars
1266
1601
  # df.select(
1267
1602
  # [
1268
1603
  # Polars.col("date"),
1269
- # Polars.col("date").diff.dt.minutes.alias("minutes_diff")
1604
+ # Polars.col("date").diff.dt.total_minutes.alias("minutes_diff")
1270
1605
  # ]
1271
1606
  # )
1272
1607
  # # =>
@@ -1281,13 +1616,15 @@ module Polars
1281
1616
  # # │ 2020-01-03 00:00:00 ┆ 1440 │
1282
1617
  # # │ 2020-01-04 00:00:00 ┆ 1440 │
1283
1618
  # # └─────────────────────┴──────────────┘
1284
- def total_minutes
1285
- Utils.wrap_expr(_rbexpr.dt_total_minutes)
1619
+ def total_minutes(fractional: false)
1620
+ Utils.wrap_expr(_rbexpr.dt_total_minutes(fractional))
1286
1621
  end
1287
- alias_method :minutes, :total_minutes
1288
1622
 
1289
1623
  # Extract the seconds from a Duration type.
1290
1624
  #
1625
+ # @param fractional [Boolean]
1626
+ # Whether to include the fractional component of the second.
1627
+ #
1291
1628
  # @return [Expr]
1292
1629
  #
1293
1630
  # @example
@@ -1301,7 +1638,7 @@ module Polars
1301
1638
  # df.select(
1302
1639
  # [
1303
1640
  # Polars.col("date"),
1304
- # Polars.col("date").diff.dt.seconds.alias("seconds_diff")
1641
+ # Polars.col("date").diff.dt.total_seconds.alias("seconds_diff")
1305
1642
  # ]
1306
1643
  # )
1307
1644
  # # =>
@@ -1317,13 +1654,15 @@ module Polars
1317
1654
  # # │ 2020-01-01 00:03:00 ┆ 60 │
1318
1655
  # # │ 2020-01-01 00:04:00 ┆ 60 │
1319
1656
  # # └─────────────────────┴──────────────┘
1320
- def total_seconds
1321
- Utils.wrap_expr(_rbexpr.dt_total_seconds)
1657
+ def total_seconds(fractional: false)
1658
+ Utils.wrap_expr(_rbexpr.dt_total_seconds(fractional))
1322
1659
  end
1323
- alias_method :seconds, :total_seconds
1324
1660
 
1325
1661
  # Extract the milliseconds from a Duration type.
1326
1662
  #
1663
+ # @param fractional [Boolean]
1664
+ # Whether to include the fractional component of the second.
1665
+ #
1327
1666
  # @return [Expr]
1328
1667
  #
1329
1668
  # @example
@@ -1337,7 +1676,7 @@ module Polars
1337
1676
  # df.select(
1338
1677
  # [
1339
1678
  # Polars.col("date"),
1340
- # Polars.col("date").diff.dt.milliseconds.alias("milliseconds_diff")
1679
+ # Polars.col("date").diff.dt.total_milliseconds.alias("milliseconds_diff")
1341
1680
  # ]
1342
1681
  # )
1343
1682
  # # =>
@@ -1359,13 +1698,15 @@ module Polars
1359
1698
  # # │ 2020-01-01 00:00:00.999 ┆ 1 │
1360
1699
  # # │ 2020-01-01 00:00:01 ┆ 1 │
1361
1700
  # # └─────────────────────────┴───────────────────┘
1362
- def total_milliseconds
1363
- Utils.wrap_expr(_rbexpr.dt_total_milliseconds)
1701
+ def total_milliseconds(fractional: false)
1702
+ Utils.wrap_expr(_rbexpr.dt_total_milliseconds(fractional))
1364
1703
  end
1365
- alias_method :milliseconds, :total_milliseconds
1366
1704
 
1367
1705
  # Extract the microseconds from a Duration type.
1368
1706
  #
1707
+ # @param fractional [Boolean]
1708
+ # Whether to include the fractional component of the second.
1709
+ #
1369
1710
  # @return [Expr]
1370
1711
  #
1371
1712
  # @example
@@ -1379,7 +1720,7 @@ module Polars
1379
1720
  # df.select(
1380
1721
  # [
1381
1722
  # Polars.col("date"),
1382
- # Polars.col("date").diff.dt.microseconds.alias("microseconds_diff")
1723
+ # Polars.col("date").diff.dt.total_microseconds.alias("microseconds_diff")
1383
1724
  # ]
1384
1725
  # )
1385
1726
  # # =>
@@ -1401,13 +1742,15 @@ module Polars
1401
1742
  # # │ 2020-01-01 00:00:00.999 ┆ 1000 │
1402
1743
  # # │ 2020-01-01 00:00:01 ┆ 1000 │
1403
1744
  # # └─────────────────────────┴───────────────────┘
1404
- def total_microseconds
1405
- Utils.wrap_expr(_rbexpr.dt_total_microseconds)
1745
+ def total_microseconds(fractional: false)
1746
+ Utils.wrap_expr(_rbexpr.dt_total_microseconds(fractional))
1406
1747
  end
1407
- alias_method :microseconds, :total_microseconds
1408
1748
 
1409
1749
  # Extract the nanoseconds from a Duration type.
1410
1750
  #
1751
+ # @param fractional [Boolean]
1752
+ # Whether to include the fractional component of the second.
1753
+ #
1411
1754
  # @return [Expr]
1412
1755
  #
1413
1756
  # @example
@@ -1421,7 +1764,7 @@ module Polars
1421
1764
  # df.select(
1422
1765
  # [
1423
1766
  # Polars.col("date"),
1424
- # Polars.col("date").diff.dt.nanoseconds.alias("nanoseconds_diff")
1767
+ # Polars.col("date").diff.dt.total_nanoseconds.alias("nanoseconds_diff")
1425
1768
  # ]
1426
1769
  # )
1427
1770
  # # =>
@@ -1443,10 +1786,9 @@ module Polars
1443
1786
  # # │ 2020-01-01 00:00:00.999 ┆ 1000000 │
1444
1787
  # # │ 2020-01-01 00:00:01 ┆ 1000000 │
1445
1788
  # # └─────────────────────────┴──────────────────┘
1446
- def total_nanoseconds
1447
- Utils.wrap_expr(_rbexpr.dt_total_nanoseconds)
1789
+ def total_nanoseconds(fractional: false)
1790
+ Utils.wrap_expr(_rbexpr.dt_total_nanoseconds(fractional))
1448
1791
  end
1449
- alias_method :nanoseconds, :total_nanoseconds
1450
1792
 
1451
1793
  # Offset this date by a relative time offset.
1452
1794
  #