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,16 +1,14 @@
1
- use magnus::{prelude::*, Value};
2
- use polars::io::avro::AvroCompression;
1
+ use magnus::{Value, prelude::*};
3
2
  use polars::io::RowIndex;
3
+ use polars::io::avro::AvroCompression;
4
4
  use polars::prelude::*;
5
- use polars_utils::mmap::ensure_not_mapped;
6
5
  use std::io::BufWriter;
7
6
  use std::num::NonZeroUsize;
8
7
 
9
8
  use super::*;
10
9
  use crate::conversion::*;
11
10
  use crate::file::{
12
- get_either_file, get_file_like, get_mmap_bytes_reader, get_mmap_bytes_reader_and_path,
13
- read_if_bytesio, EitherRustRubyFile,
11
+ get_file_like, get_mmap_bytes_reader, get_mmap_bytes_reader_and_path, read_if_bytesio,
14
12
  };
15
13
  use crate::{RbPolarsErr, RbResult};
16
14
 
@@ -25,28 +23,29 @@ impl RbDataFrame {
25
23
  let ignore_errors = bool::try_convert(arguments[4])?;
26
24
  let n_rows = Option::<usize>::try_convert(arguments[5])?;
27
25
  let skip_rows = usize::try_convert(arguments[6])?;
28
- let projection = Option::<Vec<usize>>::try_convert(arguments[7])?;
29
- let separator = String::try_convert(arguments[8])?;
30
- let rechunk = bool::try_convert(arguments[9])?;
31
- let columns = Option::<Vec<String>>::try_convert(arguments[10])?;
32
- let encoding = Wrap::<CsvEncoding>::try_convert(arguments[11])?;
33
- let n_threads = Option::<usize>::try_convert(arguments[12])?;
34
- let path = Option::<String>::try_convert(arguments[13])?;
35
- let overwrite_dtype = Option::<Vec<(String, Wrap<DataType>)>>::try_convert(arguments[14])?;
36
- let overwrite_dtype_slice = Option::<Vec<Wrap<DataType>>>::try_convert(arguments[15])?;
37
- let low_memory = bool::try_convert(arguments[16])?;
38
- let comment_prefix = Option::<String>::try_convert(arguments[17])?;
39
- let quote_char = Option::<String>::try_convert(arguments[18])?;
40
- let null_values = Option::<Wrap<NullValues>>::try_convert(arguments[19])?;
41
- let missing_utf8_is_empty_string = bool::try_convert(arguments[20])?;
42
- let try_parse_dates = bool::try_convert(arguments[21])?;
43
- let skip_rows_after_header = usize::try_convert(arguments[22])?;
44
- let row_index = Option::<(String, IdxSize)>::try_convert(arguments[23])?;
45
- let eol_char = String::try_convert(arguments[24])?;
46
- let raise_if_empty = bool::try_convert(arguments[25])?;
47
- let truncate_ragged_lines = bool::try_convert(arguments[26])?;
48
- let decimal_comma = bool::try_convert(arguments[27])?;
49
- let schema = Option::<Wrap<Schema>>::try_convert(arguments[28])?;
26
+ let skip_lines = usize::try_convert(arguments[7])?;
27
+ let projection = Option::<Vec<usize>>::try_convert(arguments[8])?;
28
+ let separator = String::try_convert(arguments[9])?;
29
+ let rechunk = bool::try_convert(arguments[10])?;
30
+ let columns = Option::<Vec<String>>::try_convert(arguments[11])?;
31
+ let encoding = Wrap::<CsvEncoding>::try_convert(arguments[12])?;
32
+ let n_threads = Option::<usize>::try_convert(arguments[13])?;
33
+ let path = Option::<String>::try_convert(arguments[14])?;
34
+ let overwrite_dtype = Option::<Vec<(String, Wrap<DataType>)>>::try_convert(arguments[15])?;
35
+ let overwrite_dtype_slice = Option::<Vec<Wrap<DataType>>>::try_convert(arguments[16])?;
36
+ let low_memory = bool::try_convert(arguments[17])?;
37
+ let comment_prefix = Option::<String>::try_convert(arguments[18])?;
38
+ let quote_char = Option::<String>::try_convert(arguments[19])?;
39
+ let null_values = Option::<Wrap<NullValues>>::try_convert(arguments[20])?;
40
+ let missing_utf8_is_empty_string = bool::try_convert(arguments[21])?;
41
+ let try_parse_dates = bool::try_convert(arguments[22])?;
42
+ let skip_rows_after_header = usize::try_convert(arguments[23])?;
43
+ let row_index = Option::<(String, IdxSize)>::try_convert(arguments[24])?;
44
+ let eol_char = String::try_convert(arguments[25])?;
45
+ let raise_if_empty = bool::try_convert(arguments[26])?;
46
+ let truncate_ragged_lines = bool::try_convert(arguments[27])?;
47
+ let decimal_comma = bool::try_convert(arguments[28])?;
48
+ let schema = Option::<Wrap<Schema>>::try_convert(arguments[29])?;
50
49
  // end arguments
51
50
 
52
51
  let null_values = null_values.map(|w| w.0);
@@ -90,6 +89,7 @@ impl RbDataFrame {
90
89
  .with_has_header(has_header)
91
90
  .with_n_rows(n_rows)
92
91
  .with_skip_rows(skip_rows)
92
+ .with_skip_lines(skip_lines)
93
93
  .with_ignore_errors(ignore_errors)
94
94
  .with_projection(projection.map(Arc::new))
95
95
  .with_rechunk(rechunk)
@@ -147,33 +147,6 @@ impl RbDataFrame {
147
147
  Ok(out.into())
148
148
  }
149
149
 
150
- pub fn read_ndjson(
151
- rb_f: Value,
152
- ignore_errors: bool,
153
- schema: Option<Wrap<Schema>>,
154
- schema_overrides: Option<Wrap<Schema>>,
155
- ) -> RbResult<Self> {
156
- let rb_f = read_if_bytesio(rb_f);
157
- let mmap_bytes_r = get_mmap_bytes_reader(&rb_f)?;
158
-
159
- let mut builder = JsonReader::new(mmap_bytes_r)
160
- .with_json_format(JsonFormat::JsonLines)
161
- .with_ignore_errors(ignore_errors);
162
-
163
- if let Some(schema) = schema {
164
- builder = builder.with_schema(Arc::new(schema.0));
165
- }
166
-
167
- if let Some(schema) = schema_overrides.as_ref() {
168
- builder = builder.with_schema_overwrite(&schema.0);
169
- }
170
-
171
- let out = builder
172
- .finish()
173
- .map_err(|e| RbPolarsErr::Other(format!("{e}")))?;
174
- Ok(out.into())
175
- }
176
-
177
150
  pub fn read_ipc(
178
151
  rb_f: Value,
179
152
  columns: Option<Vec<String>>,
@@ -244,102 +217,12 @@ impl RbDataFrame {
244
217
  Ok(RbDataFrame::new(df))
245
218
  }
246
219
 
247
- #[allow(clippy::too_many_arguments)]
248
- pub fn write_csv(
249
- &self,
250
- rb_f: Value,
251
- include_header: bool,
252
- separator: u8,
253
- quote_char: u8,
254
- batch_size: Wrap<NonZeroUsize>,
255
- datetime_format: Option<String>,
256
- date_format: Option<String>,
257
- time_format: Option<String>,
258
- float_precision: Option<usize>,
259
- null_value: Option<String>,
260
- ) -> RbResult<()> {
261
- let batch_size = batch_size.0;
262
- let null = null_value.unwrap_or_default();
263
- let mut buf = get_file_like(rb_f, true)?;
264
- CsvWriter::new(&mut buf)
265
- .include_header(include_header)
266
- .with_separator(separator)
267
- .with_quote_char(quote_char)
268
- .with_batch_size(batch_size)
269
- .with_datetime_format(datetime_format)
270
- .with_date_format(date_format)
271
- .with_time_format(time_format)
272
- .with_float_precision(float_precision)
273
- .with_null_value(null)
274
- .finish(&mut self.df.borrow_mut())
275
- .map_err(RbPolarsErr::from)?;
276
- Ok(())
277
- }
278
-
279
- pub fn write_parquet(
280
- &self,
281
- rb_f: Value,
282
- compression: String,
283
- compression_level: Option<i32>,
284
- statistics: Wrap<StatisticsOptions>,
285
- row_group_size: Option<usize>,
286
- data_page_size: Option<usize>,
287
- ) -> RbResult<()> {
288
- let compression = parse_parquet_compression(&compression, compression_level)?;
289
-
290
- let buf = get_file_like(rb_f, true)?;
291
- ParquetWriter::new(buf)
292
- .with_compression(compression)
293
- .with_statistics(statistics.0)
294
- .with_row_group_size(row_group_size)
295
- .with_data_page_size(data_page_size)
296
- .finish(&mut self.df.borrow_mut())
297
- .map_err(RbPolarsErr::from)?;
298
- Ok(())
299
- }
300
-
301
- pub fn write_json(&self, rb_f: Value, pretty: bool, row_oriented: bool) -> RbResult<()> {
220
+ pub fn write_json(&self, rb_f: Value) -> RbResult<()> {
302
221
  let file = BufWriter::new(get_file_like(rb_f, true)?);
303
222
 
304
- let r = match (pretty, row_oriented) {
305
- (_, true) => JsonWriter::new(file)
306
- .with_json_format(JsonFormat::Json)
307
- .finish(&mut self.df.borrow_mut()),
308
- (true, _) => serde_json::to_writer_pretty(file, &*self.df.borrow())
309
- .map_err(|e| PolarsError::ComputeError(format!("{:?}", e).into())),
310
- (false, _) => serde_json::to_writer(file, &*self.df.borrow())
311
- .map_err(|e| PolarsError::ComputeError(format!("{:?}", e).into())),
312
- };
313
- r.map_err(|e| RbPolarsErr::Other(format!("{:?}", e)))?;
314
- Ok(())
315
- }
316
-
317
- pub fn write_ndjson(&self, rb_f: Value) -> RbResult<()> {
318
- let file = BufWriter::new(get_file_like(rb_f, true)?);
319
-
320
- let r = JsonWriter::new(file)
321
- .with_json_format(JsonFormat::JsonLines)
322
- .finish(&mut self.df.borrow_mut());
323
-
324
- r.map_err(|e| RbPolarsErr::Other(format!("{:?}", e)))?;
325
- Ok(())
326
- }
327
-
328
- pub fn write_ipc(
329
- &self,
330
- rb_f: Value,
331
- compression: Wrap<Option<IpcCompression>>,
332
- compat_level: RbCompatLevel,
333
- ) -> RbResult<()> {
334
- let either = get_either_file(rb_f, true)?;
335
- if let EitherRustRubyFile::Rust(ref f) = either {
336
- ensure_not_mapped(f).map_err(RbPolarsErr::from)?;
337
- }
338
- let mut buf = either.into_dyn();
339
- IpcWriter::new(&mut buf)
340
- .with_compression(compression.0)
341
- .with_compat_level(compat_level.0)
342
- .finish(&mut self.df.borrow_mut())
223
+ JsonWriter::new(file)
224
+ .with_json_format(JsonFormat::Json)
225
+ .finish(&mut self.df.write())
343
226
  .map_err(RbPolarsErr::from)?;
344
227
  Ok(())
345
228
  }
@@ -354,7 +237,7 @@ impl RbDataFrame {
354
237
  IpcStreamWriter::new(&mut buf)
355
238
  .with_compression(compression.0)
356
239
  .with_compat_level(compat_level.0)
357
- .finish(&mut self.df.borrow_mut())
240
+ .finish(&mut self.df.write())
358
241
  .map_err(RbPolarsErr::from)?;
359
242
  Ok(())
360
243
  }
@@ -370,7 +253,7 @@ impl RbDataFrame {
370
253
  AvroWriter::new(&mut buf)
371
254
  .with_compression(compression.0)
372
255
  .with_name(name)
373
- .finish(&mut self.df.borrow_mut())
256
+ .finish(&mut self.df.write())
374
257
  .map_err(RbPolarsErr::from)?;
375
258
  Ok(())
376
259
  }
@@ -0,0 +1,175 @@
1
+ use magnus::{IntoValue, RArray, Ruby, TryConvert, Value, value::Opaque, value::ReprValue};
2
+ use polars::frame::row::{Row, rows_to_schema_first_non_null};
3
+ use polars_core::utils::CustomIterTools;
4
+
5
+ use super::*;
6
+ use crate::error::RbPolarsErr;
7
+ use crate::prelude::*;
8
+ use crate::ruby::utils::{TryIntoValue, to_pl_err};
9
+ use crate::series::construction::series_from_objects;
10
+ use crate::{RbResult, RbSeries, raise_err};
11
+
12
+ impl RbDataFrame {
13
+ pub fn map_rows(
14
+ rb: &Ruby,
15
+ self_: &Self,
16
+ lambda: Value,
17
+ output_type: Option<Wrap<DataType>>,
18
+ inference_size: usize,
19
+ ) -> RbResult<(Value, bool)> {
20
+ let df = self_.df.read();
21
+ let height = df.height();
22
+ let col_series: Vec<_> = df
23
+ .columns()
24
+ .iter()
25
+ .map(|s| s.as_materialized_series().clone())
26
+ .collect();
27
+ let mut iters: Vec<_> = col_series.iter().map(|c| c.iter()).collect();
28
+ drop(df); // Release lock before calling lambda.
29
+
30
+ let lambda_result_iter = (0..height).map(move |_| {
31
+ let iter = iters
32
+ .iter_mut()
33
+ .map(|it| Wrap(it.next().unwrap()).try_into_value_with(rb));
34
+ rb.ary_try_from_iter(iter)
35
+ .and_then(|tpl| lambda.funcall::<_, _, Value>("call", (tpl,)))
36
+ });
37
+
38
+ // Simple case: return type set.
39
+ if let Some(output_type) = &output_type {
40
+ // If the output type is Object we should not go through AnyValue.
41
+ if let DataType::Object(_) = output_type.0 {
42
+ let objects = lambda_result_iter
43
+ .map(|res| {
44
+ Ok(ObjectValue {
45
+ inner: Opaque::from(res?),
46
+ })
47
+ })
48
+ .collect::<RbResult<Vec<_>>>()?;
49
+ let s = series_from_objects(rb, PlSmallStr::from_static("map"), objects);
50
+ return Ok((RbSeries::from(s).into_value_with(rb), false));
51
+ }
52
+
53
+ let avs = lambda_result_iter
54
+ .map(|res| Wrap::<AnyValue>::try_convert(res?).map(|w| w.0))
55
+ .collect::<RbResult<Vec<AnyValue>>>()?;
56
+ let s = Series::from_any_values_and_dtype(
57
+ PlSmallStr::from_static("map"),
58
+ &avs,
59
+ &output_type.0,
60
+ true,
61
+ )
62
+ .map_err(RbPolarsErr::from)?;
63
+ return Ok((RbSeries::from(s).into_value_with(rb), false));
64
+ }
65
+
66
+ // Disambiguate returning a DataFrame vs Series by checking the
67
+ // first non-null output value.
68
+ let mut peek_iter = lambda_result_iter.peekable();
69
+ let mut null_count = 0;
70
+ while let Some(ret) = peek_iter.peek() {
71
+ if let Ok(v) = ret
72
+ && v.is_nil()
73
+ {
74
+ null_count += 1;
75
+ peek_iter.next();
76
+ } else {
77
+ break;
78
+ }
79
+ }
80
+
81
+ let first_val = match peek_iter.peek() {
82
+ Some(Ok(v)) => v,
83
+ Some(Err(e)) => return Err(e.clone()),
84
+ None => {
85
+ let msg = "The output type of the 'map_rows' function cannot be determined.\n\
86
+ All returned values are None, consider setting the 'return_dtype'.";
87
+ raise_err!(msg, ComputeError)
88
+ }
89
+ };
90
+
91
+ if let Ok(first_row) = RArray::try_convert(*first_val) {
92
+ let width = first_row.len();
93
+ let out_df = collect_lambda_ret_with_rows_output(
94
+ height,
95
+ width,
96
+ null_count,
97
+ inference_size,
98
+ peek_iter,
99
+ )
100
+ .map_err(RbPolarsErr::from)?;
101
+ Ok((RbDataFrame::from(out_df).into_value_with(rb), true))
102
+ } else {
103
+ let avs = peek_iter
104
+ .map(|res| Wrap::<AnyValue>::try_convert(res?).map(|w| w.0))
105
+ .collect::<RbResult<Vec<AnyValue>>>()?;
106
+ let s = Series::from_any_values(PlSmallStr::from_static("map"), &avs, true)
107
+ .map_err(RbPolarsErr::from)?;
108
+
109
+ let out = if null_count > 0 {
110
+ let mut tmp = Series::full_null(s.name().clone(), null_count, s.dtype());
111
+ tmp.append_owned(s).map_err(RbPolarsErr::from)?;
112
+ tmp
113
+ } else {
114
+ s
115
+ };
116
+ Ok((RbSeries::from(out).into_value_with(rb), false))
117
+ }
118
+ }
119
+ }
120
+
121
+ fn collect_lambda_ret_with_rows_output(
122
+ height: usize,
123
+ width: usize,
124
+ init_null_count: usize,
125
+ inference_size: usize,
126
+ ret_iter: impl Iterator<Item = RbResult<Value>>,
127
+ ) -> PolarsResult<DataFrame> {
128
+ let null_row = Row::new(vec![AnyValue::Null; width]);
129
+
130
+ let mut row_buf = Row::default();
131
+ let mut row_iter = ret_iter.map(|retval| {
132
+ let retval = retval.map_err(to_pl_err)?;
133
+ if retval.is_nil() {
134
+ Ok(&null_row)
135
+ } else {
136
+ let tuple = RArray::try_convert(retval).map_err(|_| polars_err!(ComputeError: format!("expected tuple, got {}", unsafe { retval.classname() })))?;
137
+ row_buf.0.clear();
138
+ for v in tuple {
139
+ let v = Wrap::<AnyValue>::try_convert(v).unwrap().0;
140
+ row_buf.0.push(v);
141
+ }
142
+ let ptr = &row_buf as *const Row;
143
+ // SAFETY:
144
+ // we know that row constructor of polars dataframe does not keep a reference
145
+ // to the row. Before we mutate the row buf again, the reference is dropped.
146
+ // we only cannot prove it to the compiler.
147
+ // we still to this because it save a Vec allocation in a hot loop.
148
+ Ok(unsafe { &*ptr })
149
+ }
150
+ });
151
+
152
+ // First rows for schema inference.
153
+ let mut buf = Vec::with_capacity(inference_size);
154
+ for v in (&mut row_iter).take(inference_size) {
155
+ buf.push(v?.clone());
156
+ }
157
+
158
+ let schema = rows_to_schema_first_non_null(&buf, Some(50))?;
159
+
160
+ if init_null_count > 0 {
161
+ // SAFETY: we know the iterators size.
162
+ let iter = unsafe {
163
+ (0..init_null_count)
164
+ .map(|_| Ok(&null_row))
165
+ .chain(buf.iter().map(Ok))
166
+ .chain(row_iter)
167
+ .trust_my_length(height)
168
+ };
169
+ DataFrame::try_from_rows_iter_and_schema(iter, &schema)
170
+ } else {
171
+ // SAFETY: we know the iterators size.
172
+ let iter = unsafe { buf.iter().map(Ok).chain(row_iter).trust_my_length(height) };
173
+ DataFrame::try_from_rows_iter_and_schema(iter, &schema)
174
+ }
175
+ }
@@ -2,26 +2,58 @@ mod construction;
2
2
  mod export;
3
3
  mod general;
4
4
  mod io;
5
+ mod map;
5
6
  mod serde;
6
7
 
8
+ use magnus::{DataTypeFunctions, TypedData, gc};
9
+ use parking_lot::RwLock;
7
10
  use polars::prelude::*;
8
- use std::cell::RefCell;
9
11
 
10
- #[magnus::wrap(class = "Polars::RbDataFrame")]
12
+ use crate::series::mark_series;
13
+
14
+ #[derive(TypedData)]
15
+ #[magnus(class = "Polars::RbDataFrame", mark)]
11
16
  pub struct RbDataFrame {
12
- pub df: RefCell<DataFrame>,
17
+ pub df: RwLock<DataFrame>,
18
+ }
19
+
20
+ impl Clone for RbDataFrame {
21
+ fn clone(&self) -> Self {
22
+ RbDataFrame {
23
+ df: RwLock::new(self.df.read().clone()),
24
+ }
25
+ }
13
26
  }
14
27
 
15
28
  impl From<DataFrame> for RbDataFrame {
16
29
  fn from(df: DataFrame) -> Self {
17
- RbDataFrame::new(df)
30
+ Self::new(df)
18
31
  }
19
32
  }
20
33
 
21
34
  impl RbDataFrame {
22
35
  pub fn new(df: DataFrame) -> Self {
23
36
  RbDataFrame {
24
- df: RefCell::new(df),
37
+ df: RwLock::new(df),
38
+ }
39
+ }
40
+ }
41
+
42
+ impl DataTypeFunctions for RbDataFrame {
43
+ fn mark(&self, marker: &gc::Marker) {
44
+ // this is really, really not ideal, as objects will not be marked if unable to borrow
45
+ // currently, this should only happen for write_* methods,
46
+ // which should refuse to write Object datatype, and therefore be safe,
47
+ // since GC will not have a chance to run
48
+ if let Some(df) = self.df.try_read() {
49
+ for column in df.columns() {
50
+ if let DataType::Object(_) = column.dtype() {
51
+ match column {
52
+ Column::Series(s) => mark_series(marker, s),
53
+ Column::Scalar(s) => mark_series(marker, &s.as_single_value_series()),
54
+ }
55
+ }
56
+ }
25
57
  }
26
58
  }
27
59
  }
@@ -1,15 +1,30 @@
1
- use crate::exceptions::ComputeError;
1
+ use std::io::{BufReader, BufWriter};
2
+
3
+ use polars::prelude::*;
4
+
2
5
  use crate::file::get_file_like;
3
- use crate::{RbDataFrame, RbResult};
6
+ use crate::utils::to_rb_err;
7
+ use crate::{RbDataFrame, RbPolarsErr, RbResult};
4
8
  use magnus::Value;
5
- use std::io::BufWriter;
6
9
 
7
10
  impl RbDataFrame {
8
- // TODO add to Ruby
9
- pub fn serialize_json(&self, rb_f: Value) -> RbResult<()> {
11
+ pub fn serialize_binary(&self, rb_f: Value) -> RbResult<()> {
10
12
  let file = get_file_like(rb_f, true)?;
11
- let writer = BufWriter::new(file);
12
- serde_json::to_writer(writer, &self.df)
13
- .map_err(|err| ComputeError::new_err(err.to_string()))
13
+ let mut writer = BufWriter::new(file);
14
+
15
+ Ok(self
16
+ .df
17
+ .write()
18
+ .serialize_into_writer(&mut writer)
19
+ .map_err(RbPolarsErr::from)?)
20
+ }
21
+
22
+ pub fn deserialize_binary(rb_f: Value) -> RbResult<Self> {
23
+ let file = get_file_like(rb_f, false)?;
24
+ let mut file = BufReader::new(file);
25
+
26
+ DataFrame::deserialize_from_reader(&mut file)
27
+ .map(|v| v.into())
28
+ .map_err(to_rb_err)
14
29
  }
15
30
  }
@@ -3,11 +3,17 @@ use std::fmt::{Debug, Formatter};
3
3
  use magnus::Error;
4
4
  use polars::prelude::PolarsError;
5
5
 
6
- use crate::exceptions::{ComputeError, InvalidOperationError};
7
- use crate::rb_modules;
6
+ use crate::RbErr;
7
+ use crate::exceptions::{
8
+ AssertionError, ColumnNotFoundError, ComputeError, DuplicateError, InvalidOperationError,
9
+ NoDataError, OutOfBoundsError, SQLInterfaceError, SQLSyntaxError, SchemaError,
10
+ SchemaFieldNotFoundError, ShapeError, StringCacheMismatchError, StructFieldNotFoundError,
11
+ };
12
+ use crate::ruby::exceptions::{RbIOError, RbRuntimeError};
8
13
 
9
14
  pub enum RbPolarsErr {
10
15
  Polars(PolarsError),
16
+ Ruby(RbErr),
11
17
  Other(String),
12
18
  }
13
19
 
@@ -17,9 +23,9 @@ impl From<PolarsError> for RbPolarsErr {
17
23
  }
18
24
  }
19
25
 
20
- impl From<std::io::Error> for RbPolarsErr {
21
- fn from(value: std::io::Error) -> Self {
22
- RbPolarsErr::Other(format!("{value:?}"))
26
+ impl From<RbErr> for RbPolarsErr {
27
+ fn from(err: RbErr) -> Self {
28
+ RbPolarsErr::Ruby(err)
23
29
  }
24
30
  }
25
31
 
@@ -27,13 +33,43 @@ impl From<RbPolarsErr> for Error {
27
33
  fn from(err: RbPolarsErr) -> Self {
28
34
  match err {
29
35
  RbPolarsErr::Polars(err) => match err {
36
+ PolarsError::AssertionError(err) => AssertionError::new_err(err.to_string()),
37
+ PolarsError::ColumnNotFound(name) => ColumnNotFoundError::new_err(name.to_string()),
30
38
  PolarsError::ComputeError(err) => ComputeError::new_err(err.to_string()),
39
+ PolarsError::Duplicate(err) => DuplicateError::new_err(err.to_string()),
31
40
  PolarsError::InvalidOperation(err) => {
32
41
  InvalidOperationError::new_err(err.to_string())
33
42
  }
34
- _ => Error::new(rb_modules::error(), err.to_string()),
43
+ PolarsError::IO { error, msg } => {
44
+ let msg = if let Some(msg) = msg {
45
+ msg.to_string()
46
+ } else {
47
+ error.to_string()
48
+ };
49
+ RbIOError::new_err(msg)
50
+ }
51
+ PolarsError::NoData(err) => NoDataError::new_err(err.to_string()),
52
+ PolarsError::OutOfBounds(err) => OutOfBoundsError::new_err(err.to_string()),
53
+ PolarsError::SQLInterface(name) => SQLInterfaceError::new_err(name.to_string()),
54
+ PolarsError::SQLSyntax(name) => SQLSyntaxError::new_err(name.to_string()),
55
+ PolarsError::SchemaFieldNotFound(name) => {
56
+ SchemaFieldNotFoundError::new_err(name.to_string())
57
+ }
58
+ PolarsError::SchemaMismatch(err) => SchemaError::new_err(err.to_string()),
59
+ PolarsError::ShapeMismatch(err) => ShapeError::new_err(err.to_string()),
60
+ PolarsError::StringCacheMismatch(err) => {
61
+ StringCacheMismatchError::new_err(err.to_string())
62
+ }
63
+ PolarsError::StructFieldNotFound(name) => {
64
+ StructFieldNotFoundError::new_err(name.to_string())
65
+ }
66
+ PolarsError::Context { .. } | PolarsError::ExprContext { .. } => {
67
+ let tmp = RbPolarsErr::Polars(err.context_trace());
68
+ RbErr::from(tmp)
69
+ }
35
70
  },
36
- RbPolarsErr::Other(err) => Error::new(rb_modules::error(), err.to_string()),
71
+ RbPolarsErr::Ruby(err) => err,
72
+ err => RbRuntimeError::new_err(format!("{:?}", &err)),
37
73
  }
38
74
  }
39
75
  }
@@ -43,6 +79,7 @@ impl Debug for RbPolarsErr {
43
79
  use RbPolarsErr::*;
44
80
  match self {
45
81
  Polars(err) => write!(f, "{err:?}"),
82
+ Ruby(err) => write!(f, "{err:?}"),
46
83
  Other(err) => write!(f, "BindingsError: {err:?}"),
47
84
  }
48
85
  }
@@ -1,9 +1,9 @@
1
1
  use crate::rb_modules;
2
- use magnus::{exception, Error};
2
+ use magnus::{Error, Module, Ruby};
3
3
  use std::borrow::Cow;
4
4
 
5
5
  macro_rules! create_exception {
6
- ($type:ident, $cls:expr) => {
6
+ ($type:ident) => {
7
7
  pub struct $type {}
8
8
 
9
9
  impl $type {
@@ -11,14 +11,27 @@ macro_rules! create_exception {
11
11
  where
12
12
  T: Into<Cow<'static, str>>,
13
13
  {
14
- Error::new($cls, message)
14
+ let ruby = Ruby::get().unwrap();
15
+ let cls = rb_modules::polars(&ruby)
16
+ .const_get(stringify!($type))
17
+ .unwrap();
18
+ Error::new(cls, message)
15
19
  }
16
20
  }
17
21
  };
18
22
  }
19
23
 
20
- create_exception!(RbTypeError, exception::type_error());
21
- create_exception!(RbValueError, exception::arg_error());
22
- create_exception!(RbOverflowError, exception::range_error());
23
- create_exception!(ComputeError, rb_modules::compute_error());
24
- create_exception!(InvalidOperationError, rb_modules::invalid_operation_error());
24
+ create_exception!(AssertionError);
25
+ create_exception!(ColumnNotFoundError);
26
+ create_exception!(ComputeError);
27
+ create_exception!(DuplicateError);
28
+ create_exception!(InvalidOperationError);
29
+ create_exception!(NoDataError);
30
+ create_exception!(OutOfBoundsError);
31
+ create_exception!(SQLInterfaceError);
32
+ create_exception!(SQLSyntaxError);
33
+ create_exception!(SchemaError);
34
+ create_exception!(SchemaFieldNotFoundError);
35
+ create_exception!(ShapeError);
36
+ create_exception!(StringCacheMismatchError);
37
+ create_exception!(StructFieldNotFoundError);