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
@@ -197,5 +197,222 @@ module Polars
197
197
  raise ArgumentError, "encoding must be one of {{'hex', 'base64'}}, got #{encoding}"
198
198
  end
199
199
  end
200
+
201
+ # Get the size of binary values in the given unit.
202
+ #
203
+ # @param unit ['b', 'kb', 'mb', 'gb', 'tb']
204
+ # Scale the returned size to the given unit.
205
+ #
206
+ # @return [Expr]
207
+ #
208
+ # @example
209
+ # df = Polars::DataFrame.new({"data" => [512, 256, 1024].map { |n| "\x00".b * n }})
210
+ # df.with_columns(
211
+ # n_bytes: Polars.col("data").bin.size,
212
+ # n_kilobytes: Polars.col("data").bin.size("kb")
213
+ # )
214
+ # # =>
215
+ # # shape: (3, 3)
216
+ # # ┌─────────────────────────────────┬─────────┬─────────────┐
217
+ # # │ data ┆ n_bytes ┆ n_kilobytes │
218
+ # # │ --- ┆ --- ┆ --- │
219
+ # # │ binary ┆ u32 ┆ f64 │
220
+ # # ╞═════════════════════════════════╪═════════╪═════════════╡
221
+ # # │ b"\x00\x00\x00\x00\x00\x00\x00… ┆ 512 ┆ 0.5 │
222
+ # # │ b"\x00\x00\x00\x00\x00\x00\x00… ┆ 256 ┆ 0.25 │
223
+ # # │ b"\x00\x00\x00\x00\x00\x00\x00… ┆ 1024 ┆ 1.0 │
224
+ # # └─────────────────────────────────┴─────────┴─────────────┘
225
+ def size(unit = "b")
226
+ sz = Utils.wrap_expr(_rbexpr.bin_size_bytes)
227
+ sz = Utils.scale_bytes(sz, to: unit)
228
+ sz
229
+ end
230
+
231
+ # Interpret a buffer as a numerical Polars type.
232
+ #
233
+ # @param dtype [Object]
234
+ # Which type to interpret binary column into.
235
+ # @param endianness : ["big", "little"]
236
+ # Which endianness to use when interpreting bytes, by default "little".
237
+ #
238
+ # @return [Expr]
239
+ #
240
+ # @example
241
+ # df = Polars::DataFrame.new({"data" => ["\x05\x00\x00\x00".b, "\x10\x00\x01\x00".b]})
242
+ # df.with_columns(
243
+ # bin2int: Polars.col("data").bin.reinterpret(
244
+ # dtype: Polars::Int32, endianness: "little"
245
+ # )
246
+ # )
247
+ # # =>
248
+ # # shape: (2, 2)
249
+ # # ┌─────────────────────┬─────────┐
250
+ # # │ data ┆ bin2int │
251
+ # # │ --- ┆ --- │
252
+ # # │ binary ┆ i32 │
253
+ # # ╞═════════════════════╪═════════╡
254
+ # # │ b"\x05\x00\x00\x00" ┆ 5 │
255
+ # # │ b"\x10\x00\x01\x00" ┆ 65552 │
256
+ # # └─────────────────────┴─────────┘
257
+ def reinterpret(
258
+ dtype:,
259
+ endianness: "little"
260
+ )
261
+ dtype = Utils.parse_into_datatype_expr(dtype)
262
+
263
+ Utils.wrap_expr(
264
+ _rbexpr.bin_reinterpret(dtype._rbdatatype_expr, endianness)
265
+ )
266
+ end
267
+
268
+ # Slice the binary values.
269
+ #
270
+ # @param offset [Object]
271
+ # Start index. Negative indexing is supported.
272
+ # @param length [Object]
273
+ # Length of the slice. If set to `nil` (default), the slice is taken to the
274
+ # end of the value.
275
+ #
276
+ # @return [Expr]
277
+ #
278
+ # @example
279
+ # colors = Polars::DataFrame.new(
280
+ # {
281
+ # "name" => ["black", "yellow", "blue"],
282
+ # "code" => ["\x00\x00\x00".b, "\xff\xff\x00".b, "\x00\x00\xff".b]
283
+ # }
284
+ # )
285
+ # colors.with_columns(
286
+ # Polars.col("code").bin.slice(1, 2).alias("sliced")
287
+ # )
288
+ # # =>
289
+ # # shape: (3, 3)
290
+ # # ┌────────┬─────────────────┬─────────────┐
291
+ # # │ name ┆ code ┆ sliced │
292
+ # # │ --- ┆ --- ┆ --- │
293
+ # # │ str ┆ binary ┆ binary │
294
+ # # ╞════════╪═════════════════╪═════════════╡
295
+ # # │ black ┆ b"\x00\x00\x00" ┆ b"\x00\x00" │
296
+ # # │ yellow ┆ b"\xff\xff\x00" ┆ b"\xff\x00" │
297
+ # # │ blue ┆ b"\x00\x00\xff" ┆ b"\x00\xff" │
298
+ # # └────────┴─────────────────┴─────────────┘
299
+ def slice(offset, length = nil)
300
+ offset_rbexpr = Utils.parse_into_expression(offset)
301
+ length_rbexpr = Utils.parse_into_expression(length)
302
+ Utils.wrap_expr(_rbexpr.bin_slice(offset_rbexpr, length_rbexpr))
303
+ end
304
+
305
+ # Take the first `n` bytes of the binary values.
306
+ #
307
+ # @param n [Object]
308
+ # Length of the slice (integer or expression). Negative indexing is supported;
309
+ # see note (2) below.
310
+ #
311
+ # @return [Expr]
312
+ #
313
+ # @note
314
+ # (1) A similar method exists for taking the last `n` bytes: :func:`tail`.
315
+ # (2) If `n` is negative, it is interpreted as "until the nth byte from the end",
316
+ # e.g., ``head(-3)`` returns all but the last three bytes.
317
+ #
318
+ # @example
319
+ # colors = Polars::DataFrame.new(
320
+ # {
321
+ # "name" => ["black", "yellow", "blue"],
322
+ # "code" => ["\x00\x00\x00".b, "\xff\xff\x00".b, "\x00\x00\xff".b]
323
+ # }
324
+ # )
325
+ # colors.with_columns(
326
+ # Polars.col("code").bin.head(2).alias("head")
327
+ # )
328
+ # # =>
329
+ # # shape: (3, 3)
330
+ # # ┌────────┬─────────────────┬─────────────┐
331
+ # # │ name ┆ code ┆ head │
332
+ # # │ --- ┆ --- ┆ --- │
333
+ # # │ str ┆ binary ┆ binary │
334
+ # # ╞════════╪═════════════════╪═════════════╡
335
+ # # │ black ┆ b"\x00\x00\x00" ┆ b"\x00\x00" │
336
+ # # │ yellow ┆ b"\xff\xff\x00" ┆ b"\xff\xff" │
337
+ # # │ blue ┆ b"\x00\x00\xff" ┆ b"\x00\x00" │
338
+ # # └────────┴─────────────────┴─────────────┘
339
+ def head(n = 5)
340
+ n_rbexpr = Utils.parse_into_expression(n, str_as_lit: false)
341
+ Utils.wrap_expr(_rbexpr.bin_head(n_rbexpr))
342
+ end
343
+
344
+ # Take the last `n` bytes of the binary values.
345
+ #
346
+ # @param n [Object]
347
+ # Length of the slice (integer or expression). Negative indexing is supported;
348
+ # see note (2) below.
349
+ #
350
+ # @return [Expr]
351
+ #
352
+ # @note
353
+ # (1) A similar method exists for taking the first `n` bytes: `head`.
354
+ # (2) If `n` is negative, it is interpreted as "starting at the nth byte",
355
+ # e.g., ``tail(-3)`` returns all but the first three bytes.
356
+ #
357
+ # @example
358
+ # colors = Polars::DataFrame.new(
359
+ # {
360
+ # "name" => ["black", "yellow", "blue"],
361
+ # "code" => ["\x00\x00\x00".b, "\xff\xff\x00".b, "\x00\x00\xff".b]
362
+ # }
363
+ # )
364
+ # colors.with_columns(
365
+ # Polars.col("code").bin.tail(2).alias("tail")
366
+ # )
367
+ # # =>
368
+ # # shape: (3, 3)
369
+ # # ┌────────┬─────────────────┬─────────────┐
370
+ # # │ name ┆ code ┆ tail │
371
+ # # │ --- ┆ --- ┆ --- │
372
+ # # │ str ┆ binary ┆ binary │
373
+ # # ╞════════╪═════════════════╪═════════════╡
374
+ # # │ black ┆ b"\x00\x00\x00" ┆ b"\x00\x00" │
375
+ # # │ yellow ┆ b"\xff\xff\x00" ┆ b"\xff\x00" │
376
+ # # │ blue ┆ b"\x00\x00\xff" ┆ b"\x00\xff" │
377
+ # # └────────┴─────────────────┴─────────────┘
378
+ def tail(n = 5)
379
+ n_rbexpr = Utils.parse_into_expression(n, str_as_lit: false)
380
+ Utils.wrap_expr(_rbexpr.bin_tail(n_rbexpr))
381
+ end
382
+
383
+ # Get the byte value at the given index.
384
+ #
385
+ # For example, index `0` would return the first byte of every binary value
386
+ # and index `-1` would return the last byte of every binary value.
387
+ # If an index is out of bounds, it will return a `None`.
388
+ #
389
+ # @param index [Object]
390
+ # Index to return per binary value
391
+ # @param null_on_oob [Boolean]
392
+ # Behavior if an index is out of bounds:
393
+ #
394
+ # * true -> set as null
395
+ # * false -> raise an error
396
+ #
397
+ # @return [Expr]
398
+ #
399
+ # @example
400
+ # df = Polars::DataFrame.new({"a" => ["\x01\x02\x03".b, "".b, "\x04\x05".b]})
401
+ # df.with_columns(get: Polars.col("a").bin.get(0, null_on_oob: true))
402
+ # # =>
403
+ # # shape: (3, 2)
404
+ # # ┌─────────────────┬──────┐
405
+ # # │ a ┆ get │
406
+ # # │ --- ┆ --- │
407
+ # # │ binary ┆ u8 │
408
+ # # ╞═════════════════╪══════╡
409
+ # # │ b"\x01\x02\x03" ┆ 1 │
410
+ # # │ b"" ┆ null │
411
+ # # │ b"\x04\x05" ┆ 4 │
412
+ # # └─────────────────┴──────┘
413
+ def get(index, null_on_oob: false)
414
+ index_rbexpr = Utils.parse_into_expression(index)
415
+ Utils.wrap_expr(_rbexpr.bin_get(index_rbexpr, null_on_oob))
416
+ end
200
417
  end
201
418
  end
@@ -110,7 +110,7 @@ module Polars
110
110
  # # b"\x00\x00\xff"
111
111
  # # ]
112
112
  #
113
- # @example Set `strict=False` to set invalid values to null instead of raising an error.
113
+ # @example Set `strict: false` to set invalid values to null instead of raising an error.
114
114
  # s = Polars::Series.new("colors", ["000000".b, "ffff00".b, "invalid_value".b])
115
115
  # s.bin.decode("hex", strict: false)
116
116
  # # =>
@@ -157,5 +157,159 @@ module Polars
157
157
  def encode(encoding)
158
158
  super
159
159
  end
160
+
161
+ # Get the size of the binary values in a Series in the given unit.
162
+ #
163
+ # @return [Series]
164
+ #
165
+ # @example
166
+ # s = Polars::Series.new("data", [512, 256, 2560, 1024].map { |n| "\x00".b * n })
167
+ # s.bin.size("kb")
168
+ # # =>
169
+ # # shape: (4,)
170
+ # # Series: 'data' [f64]
171
+ # # [
172
+ # # 0.5
173
+ # # 0.25
174
+ # # 2.5
175
+ # # 1.0
176
+ # # ]
177
+ def size(unit = "b")
178
+ super
179
+ end
180
+
181
+ # Interpret a buffer as a numerical polars type.
182
+ #
183
+ # @param dtype [Object]
184
+ # Which type to interpret binary column into.
185
+ # @param endianness ["big", "little"]
186
+ # Which endianness to use when interpreting bytes, by default "little".
187
+ #
188
+ # @return [Series]
189
+ #
190
+ # @example
191
+ # s = Polars::Series.new("data", ["\x05\x00\x00\x00".b, "\x10\x00\x01\x00".b])
192
+ # s.bin.reinterpret(dtype: Polars::Int32, endianness: "little")
193
+ # # =>
194
+ # # shape: (2,)
195
+ # # Series: 'data' [i32]
196
+ # # [
197
+ # # 5
198
+ # # 65552
199
+ # # ]
200
+ def reinterpret(dtype:, endianness: "little")
201
+ super
202
+ end
203
+
204
+ # Slice the binary values.
205
+ #
206
+ # @param offset [Object]
207
+ # Start index. Negative indexing is supported.
208
+ # @param length [Object]
209
+ # Length of the slice. If set to `nil` (default), the slice is taken to the
210
+ # end of the value.
211
+ #
212
+ # @return [Series]
213
+ #
214
+ # @example
215
+ # colors = Polars::Series.new(["\x00\x00\x00".b, "\xff\xff\x00".b, "\x00\x00\xff".b])
216
+ # colors.bin.slice(1, 2)
217
+ # # =>
218
+ # # shape: (3,)
219
+ # # Series: '' [binary]
220
+ # # [
221
+ # # b"\x00\x00"
222
+ # # b"\xff\x00"
223
+ # # b"\x00\xff"
224
+ # # ]
225
+ def slice(offset, length = nil)
226
+ super
227
+ end
228
+
229
+ # Get the byte value at the given index.
230
+ #
231
+ # For example, index `0` would return the first byte of every binary value
232
+ # and index `-1` would return the last byte of every binary value.
233
+ # The behavior if an index is out of bounds is determined by the argument
234
+ # `null_on_oob`.
235
+ #
236
+ # @param index [Object]
237
+ # Index to return per binary value
238
+ # @param null_on_oob [Boolean]
239
+ # Behavior if an index is out of bounds:
240
+ #
241
+ # * true -> set as null
242
+ # * false -> raise an error
243
+ #
244
+ # @return [Series]
245
+ #
246
+ # @example
247
+ # s = Polars::Series.new("a", ["\x01\x02\x03".b, "".b, "\x04\x05".b])
248
+ # s.bin.get(0, null_on_oob: true)
249
+ # # =>
250
+ # # shape: (3,)
251
+ # # Series: 'a' [u8]
252
+ # # [
253
+ # # 1
254
+ # # null
255
+ # # 4
256
+ # # ]
257
+ def get(index, null_on_oob: false)
258
+ super
259
+ end
260
+
261
+ # Take the first `n` bytes of the binary values.
262
+ #
263
+ # @param n [Object]
264
+ # Length of the slice. Negative indexing is supported; see note (2) below.
265
+ #
266
+ # @return [Series]
267
+ #
268
+ # @note
269
+ # (1) A similar method exists for taking the last `n` bytes: `tail`.
270
+ # (2) If `n` is negative, it is interpreted as "until the nth byte from the end",
271
+ # e.g., `head(-3)` returns all but the last three bytes.
272
+ #
273
+ # @example
274
+ # colors = Polars::Series.new(["\x00\x00\x00".b, "\xff\xff\x00".b, "\x00\x00\xff".b])
275
+ # colors.bin.head(2)
276
+ # # =>
277
+ # # shape: (3,)
278
+ # # Series: '' [binary]
279
+ # # [
280
+ # # b"\x00\x00"
281
+ # # b"\xff\xff"
282
+ # # b"\x00\x00"
283
+ # # ]
284
+ def head(n = 5)
285
+ super
286
+ end
287
+
288
+ # Take the last `n` bytes of the binary values.
289
+ #
290
+ # @param n [Object]
291
+ # Length of the slice. Negative indexing is supported; see note (2) below.
292
+ #
293
+ # @return [Series]
294
+ #
295
+ # @note
296
+ # (1) A similar method exists for taking the first `n` bytes: `head`.
297
+ # (2) If `n` is negative, it is interpreted as "starting at the nth byte",
298
+ # e.g., `tail(-3)` returns all but the first three bytes.
299
+ #
300
+ # @example
301
+ # colors = Polars::Series.new(["\x00\x00\x00".b, "\xff\xff\x00".b, "\x00\x00\xff".b])
302
+ # colors.bin.tail(2)
303
+ # # =>
304
+ # # shape: (3,)
305
+ # # Series: '' [binary]
306
+ # # [
307
+ # # b"\x00\x00"
308
+ # # b"\xff\x00"
309
+ # # b"\x00\xff"
310
+ # # ]
311
+ def tail(n = 5)
312
+ super
313
+ end
160
314
  end
161
315
  end
@@ -32,5 +32,229 @@ module Polars
32
32
  def get_categories
33
33
  Utils.wrap_expr(_rbexpr.cat_get_categories)
34
34
  end
35
+
36
+ # Return the byte-length of the string representation of each value.
37
+ #
38
+ # @return [Expr]
39
+ #
40
+ # @note
41
+ # When working with non-ASCII text, the length in bytes is not the same as the
42
+ # length in characters. You may want to use `len_chars` instead.
43
+ # Note that `len_bytes` is much more performant (_O(1)_) than
44
+ # `len_chars` (_O(n)_).
45
+ #
46
+ # @example
47
+ # df = Polars::DataFrame.new(
48
+ # {"a" => Polars::Series.new(["Café", "345", "東京", nil], dtype: Polars::Categorical)}
49
+ # )
50
+ # df.with_columns(
51
+ # Polars.col("a").cat.len_bytes.alias("n_bytes"),
52
+ # Polars.col("a").cat.len_chars.alias("n_chars")
53
+ # )
54
+ # # =>
55
+ # # shape: (4, 3)
56
+ # # ┌──────┬─────────┬─────────┐
57
+ # # │ a ┆ n_bytes ┆ n_chars │
58
+ # # │ --- ┆ --- ┆ --- │
59
+ # # │ cat ┆ u32 ┆ u32 │
60
+ # # ╞══════╪═════════╪═════════╡
61
+ # # │ Café ┆ 5 ┆ 4 │
62
+ # # │ 345 ┆ 3 ┆ 3 │
63
+ # # │ 東京 ┆ 6 ┆ 2 │
64
+ # # │ null ┆ null ┆ null │
65
+ # # └──────┴─────────┴─────────┘
66
+ def len_bytes
67
+ Utils.wrap_expr(_rbexpr.cat_len_bytes)
68
+ end
69
+
70
+ # Return the number of characters of the string representation of each value.
71
+ #
72
+ # @return [Expr]
73
+ #
74
+ # @note
75
+ # When working with ASCII text, use `len_bytes` instead to achieve
76
+ # equivalent output with much better performance:
77
+ # `len_bytes` runs in _O(1)_, while `len_chars` runs in (_O(n)_).
78
+ #
79
+ # A character is defined as a [Unicode scalar value](https://www.unicode.org/glossary/#unicode_scalar_value). A single character is
80
+ # represented by a single byte when working with ASCII text, and a maximum of
81
+ # 4 bytes otherwise.
82
+ #
83
+ # @example
84
+ # df = Polars::DataFrame.new(
85
+ # {"a" => Polars::Series.new(["Café", "345", "東京", nil], dtype: Polars::Categorical)}
86
+ # )
87
+ # df.with_columns(
88
+ # Polars.col("a").cat.len_chars.alias("n_chars"),
89
+ # Polars.col("a").cat.len_bytes.alias("n_bytes")
90
+ # )
91
+ # # =>
92
+ # # shape: (4, 3)
93
+ # # ┌──────┬─────────┬─────────┐
94
+ # # │ a ┆ n_chars ┆ n_bytes │
95
+ # # │ --- ┆ --- ┆ --- │
96
+ # # │ cat ┆ u32 ┆ u32 │
97
+ # # ╞══════╪═════════╪═════════╡
98
+ # # │ Café ┆ 4 ┆ 5 │
99
+ # # │ 345 ┆ 3 ┆ 3 │
100
+ # # │ 東京 ┆ 2 ┆ 6 │
101
+ # # │ null ┆ null ┆ null │
102
+ # # └──────┴─────────┴─────────┘
103
+ def len_chars
104
+ Utils.wrap_expr(_rbexpr.cat_len_chars)
105
+ end
106
+
107
+ # Check if string representations of values start with a substring.
108
+ #
109
+ # @param prefix [String]
110
+ # Prefix substring.
111
+ #
112
+ # @return [Expr]
113
+ #
114
+ # @note
115
+ # Whereas `str.starts_with` allows expression inputs, `cat.starts_with` requires
116
+ # a literal string value.
117
+ #
118
+ # @example
119
+ # df = Polars::DataFrame.new(
120
+ # {"fruits" => Polars::Series.new(["apple", "mango", nil], dtype: Polars::Categorical)}
121
+ # )
122
+ # df.with_columns(
123
+ # Polars.col("fruits").cat.starts_with("app").alias("has_prefix")
124
+ # )
125
+ # # =>
126
+ # # shape: (3, 2)
127
+ # # ┌────────┬────────────┐
128
+ # # │ fruits ┆ has_prefix │
129
+ # # │ --- ┆ --- │
130
+ # # │ cat ┆ bool │
131
+ # # ╞════════╪════════════╡
132
+ # # │ apple ┆ true │
133
+ # # │ mango ┆ false │
134
+ # # │ null ┆ null │
135
+ # # └────────┴────────────┘
136
+ #
137
+ # @example Using `starts_with` as a filter condition:
138
+ # df.filter(Polars.col("fruits").cat.starts_with("app"))
139
+ # # =>
140
+ # # shape: (1, 1)
141
+ # # ┌────────┐
142
+ # # │ fruits │
143
+ # # │ --- │
144
+ # # │ cat │
145
+ # # ╞════════╡
146
+ # # │ apple │
147
+ # # └────────┘
148
+ def starts_with(prefix)
149
+ if !prefix.is_a?(::String)
150
+ msg = "'prefix' must be a string; found #{prefix.inspect}"
151
+ raise TypeError, msg
152
+ end
153
+ Utils.wrap_expr(_rbexpr.cat_starts_with(prefix))
154
+ end
155
+
156
+ # Check if string representations of values end with a substring.
157
+ #
158
+ # @param suffix [String]
159
+ # Suffix substring.
160
+ #
161
+ # @return [Expr]
162
+ #
163
+ # @note
164
+ # Whereas `str.ends_with` allows expression inputs, `cat.ends_with` requires a
165
+ # literal string value.
166
+ #
167
+ # @example
168
+ # df = Polars::DataFrame.new(
169
+ # {"fruits" => Polars::Series.new(["apple", "mango", nil], dtype: Polars::Categorical)}
170
+ # )
171
+ # df.with_columns(Polars.col("fruits").cat.ends_with("go").alias("has_suffix"))
172
+ # # =>
173
+ # # shape: (3, 2)
174
+ # # ┌────────┬────────────┐
175
+ # # │ fruits ┆ has_suffix │
176
+ # # │ --- ┆ --- │
177
+ # # │ cat ┆ bool │
178
+ # # ╞════════╪════════════╡
179
+ # # │ apple ┆ false │
180
+ # # │ mango ┆ true │
181
+ # # │ null ┆ null │
182
+ # # └────────┴────────────┘
183
+ #
184
+ # @example Using `ends_with` as a filter condition:
185
+ # df.filter(Polars.col("fruits").cat.ends_with("go"))
186
+ # # =>
187
+ # # shape: (1, 1)
188
+ # # ┌────────┐
189
+ # # │ fruits │
190
+ # # │ --- │
191
+ # # │ cat │
192
+ # # ╞════════╡
193
+ # # │ mango │
194
+ # # └────────┘
195
+ def ends_with(suffix)
196
+ if !suffix.is_a?(::String)
197
+ msg = "'suffix' must be a string; found #{suffix.inspect}"
198
+ raise TypeError, msg
199
+ end
200
+ Utils.wrap_expr(_rbexpr.cat_ends_with(suffix))
201
+ end
202
+
203
+ # Extract a substring from the string representation of each value.
204
+ #
205
+ # @param offset [Integer]
206
+ # Start index. Negative indexing is supported.
207
+ # @param length [Integer]
208
+ # Length of the slice. If set to `nil` (default), the slice is taken to the
209
+ # end of the string.
210
+ #
211
+ # @return [Expr]
212
+ #
213
+ # @note
214
+ # Both the `offset` and `length` inputs are defined in terms of the number
215
+ # of characters in the (UTF8) string. A character is defined as a
216
+ # [Unicode scalar value](https://www.unicode.org/glossary/#unicode_scalar_value). A single character is represented by a single byte
217
+ # when working with ASCII text, and a maximum of 4 bytes otherwise.
218
+ #
219
+ # @example
220
+ # df = Polars::DataFrame.new(
221
+ # {
222
+ # "s" => Polars::Series.new(
223
+ # ["pear", nil, "papaya", "dragonfruit"],
224
+ # dtype: Polars::Categorical
225
+ # )
226
+ # }
227
+ # )
228
+ # df.with_columns(Polars.col("s").cat.slice(-3).alias("slice"))
229
+ # # =>
230
+ # # shape: (4, 2)
231
+ # # ┌─────────────┬───────┐
232
+ # # │ s ┆ slice │
233
+ # # │ --- ┆ --- │
234
+ # # │ cat ┆ str │
235
+ # # ╞═════════════╪═══════╡
236
+ # # │ pear ┆ ear │
237
+ # # │ null ┆ null │
238
+ # # │ papaya ┆ aya │
239
+ # # │ dragonfruit ┆ uit │
240
+ # # └─────────────┴───────┘
241
+ #
242
+ # @example Using the optional `length` parameter
243
+ # df.with_columns(Polars.col("s").cat.slice(4, 3).alias("slice"))
244
+ # # =>
245
+ # # shape: (4, 2)
246
+ # # ┌─────────────┬───────┐
247
+ # # │ s ┆ slice │
248
+ # # │ --- ┆ --- │
249
+ # # │ cat ┆ str │
250
+ # # ╞═════════════╪═══════╡
251
+ # # │ pear ┆ │
252
+ # # │ null ┆ null │
253
+ # # │ papaya ┆ ya │
254
+ # # │ dragonfruit ┆ onf │
255
+ # # └─────────────┴───────┘
256
+ def slice(offset, length = nil)
257
+ Utils.wrap_expr(_rbexpr.cat_slice(offset, length))
258
+ end
35
259
  end
36
260
  end