1use super::*;
5
6impl Column {
7 pub fn cumsum(&self) -> Result<Column> {
10 match self {
11 Column::Bool(v, val) => {
12 let iv = widen_i64(v);
13 let out = if val.has_nulls() {
14 cum_valid(&iv, val, 0, i64::wrapping_add)
15 } else {
16 stats::cumsum(&iv)
17 };
18 Ok(Column::i64_with(out, val.clone()))
19 }
20 Column::I64(v, val) => Ok(cum(v, val, stats::cumsum, i64::wrapping_add)),
21 Column::I32(v, val) => Ok(cum(v, val, stats::cumsum, i32::wrapping_add)),
22 _ => numeric_dispatch!(self, v => Numeric::into_column(stats::cumsum(v))),
23 }
24 }
25
26 pub fn cummax(&self) -> Result<Column> {
29 match self {
30 Column::Bool(v, val) => {
31 let out = if val.has_nulls() {
32 cum_valid(v, val, false, |a, b| a || b)
33 } else {
34 bool_running(v, true)
35 };
36 Ok(Column::bool_with(out, val.clone()))
37 }
38 Column::I64(v, val) => Ok(cum(v, val, stats::cummax, |a, x| if x > a { x } else { a })),
39 Column::I32(v, val) => Ok(cum(v, val, stats::cummax, |a, x| if x > a { x } else { a })),
40 Column::Str(..) | Column::Datetime(..) => self.cum_extreme(true),
43 _ => numeric_dispatch!(self, v => Numeric::into_column(stats::cummax(v))),
44 }
45 }
46
47 pub fn cummin(&self) -> Result<Column> {
50 match self {
51 Column::Bool(v, val) => {
52 let out = if val.has_nulls() {
53 cum_valid(v, val, true, |a, b| a && b)
54 } else {
55 bool_running(v, false)
56 };
57 Ok(Column::bool_with(out, val.clone()))
58 }
59 Column::I64(v, val) => Ok(cum(v, val, stats::cummin, |a, x| if x < a { x } else { a })),
60 Column::I32(v, val) => Ok(cum(v, val, stats::cummin, |a, x| if x < a { x } else { a })),
61 Column::Str(..) | Column::Datetime(..) => self.cum_extreme(false),
62 _ => numeric_dispatch!(self, v => Numeric::into_column(stats::cummin(v))),
63 }
64 }
65
66 pub fn cumprod(&self) -> Result<Column> {
69 match self {
70 Column::Bool(v, val) => {
71 let iv = widen_i64(v);
72 let out = if val.has_nulls() {
73 cum_valid(&iv, val, 1, i64::wrapping_mul)
74 } else {
75 stats::cumprod(&iv)
76 };
77 Ok(Column::i64_with(out, val.clone()))
78 }
79 Column::I64(v, val) => Ok(cum(v, val, stats::cumprod, i64::wrapping_mul)),
80 Column::I32(v, val) => Ok(cum(v, val, stats::cumprod, i32::wrapping_mul)),
81 _ => numeric_dispatch!(self, v => Numeric::into_column(stats::cumprod(v))),
82 }
83 }
84
85 pub fn abs(&self) -> Result<Column> {
89 if matches!(self, Column::Bool(_, _)) {
90 return Ok(self.clone());
91 }
92 let validity = self.validity().cloned().unwrap_or_default();
93 let out = numeric_dispatch!(self, v => Numeric::into_column(stats::abs(v)))?;
94 Ok(out.with_validity(validity))
95 }
96
97 pub fn round(&self, decimals: i32) -> Result<Column> {
101 match self {
102 Column::F64(v) => Ok(Column::f64(
103 v.iter().map(|&x| round_f64(x, decimals)).collect(),
104 )),
105 Column::F32(v) => Ok(Column::f32(
106 v.iter()
107 .map(|&x| round_f64(x as f64, decimals) as f32)
108 .collect(),
109 )),
110 Column::I64(v, val) => Ok(Column::i64_with(
111 v.iter().map(|&x| round_i64(x, decimals)).collect(),
112 val.clone(),
113 )),
114 Column::I32(v, val) => Ok(Column::i32_with(
115 v.iter()
116 .map(|&x| round_i64(x as i64, decimals) as i32)
117 .collect(),
118 val.clone(),
119 )),
120 Column::Bool(_, _) => Ok(self.clone()), other => Err(VolasError::DType(format!(
122 "cannot round a {} column",
123 other.dtype()
124 ))),
125 }
126 }
127
128 pub fn clip(&self, lower: Option<f64>, upper: Option<f64>) -> Result<Column> {
132 match self {
133 Column::Bool(v, val) => {
136 return Ok(Column::bool_with(clip_bool(v, lower, upper), val.clone()))
137 }
138 Column::F64(_) | Column::F32(_) | Column::I64(_, _) | Column::I32(_, _) => {}
139 other => {
140 return Err(VolasError::DType(format!(
141 "cannot clip a {} column",
142 other.dtype()
143 )))
144 }
145 }
146 let bound_fits = |b: Option<f64>| b.is_none_or(|x| fits(self.dtype(), x));
147 let stay = self.dtype().is_float() || (bound_fits(lower) && bound_fits(upper));
150 if stay {
151 let validity = self.validity().cloned().unwrap_or_default();
152 numeric_dispatch!(self, v => Numeric::into_column(clip_vec(v, lower, upper)))
153 .map(|c| c.with_validity(validity))
154 } else {
155 Ok(Column::f64(clip_vec(&self.to_f64_vec(), lower, upper)))
157 }
158 }
159
160 pub fn shift(&self, n: isize) -> Column {
165 let len = self.len();
166 let nulls = || {
170 Validity::from_valid_iter(
171 len,
172 (0..len).map(|i| {
173 let s = i as isize - n;
174 s >= 0 && (s as usize) < len && self.is_valid(s as usize)
175 }),
176 )
177 };
178 match self {
179 Column::F64(v) => Column::f64(shift_fill(v, n, f64::NAN)),
180 Column::F32(v) => Column::f32(shift_fill(v, n, f32::NAN)),
181 Column::I64(v, _) => Column::i64_with(shift_fill(v, n, 0), nulls()),
182 Column::I32(v, _) => Column::i32_with(shift_fill(v, n, 0), nulls()),
183 Column::Bool(v, _) => Column::bool_with(shift_fill(v, n, false), nulls()),
184 Column::Datetime(v) => Column::datetime(shift_fill(v, n, i64::MIN)),
185 Column::Str(v, _) => {
186 let shifted = (0..len)
187 .map(|i| {
188 let s = i as isize - n;
189 if s >= 0 && (s as usize) < len {
190 v.get(s as usize)
191 } else {
192 "" }
194 })
195 .collect();
196 Column::Str(shifted, nulls())
197 }
198 }
199 }
200
201 pub fn diff(&self, n: isize) -> Result<Column> {
207 match self {
208 Column::F64(v) => Ok(Column::f64(diff_kernel(v, n, f64::NAN))),
209 Column::F32(v) => Ok(Column::f32(diff_kernel(v, n, f32::NAN))),
210 _ => self.binary(&self.shift(n), BinOp::Sub),
213 }
214 }
215
216 pub fn fillna(&self, value: f64) -> Result<Column> {
220 if self.null_count() == 0 {
221 return Ok(self.clone());
222 }
223 let len = self.len();
224 Ok(match self {
225 Column::F64(v) => Column::f64(
226 v.iter()
227 .map(|&x| if x.is_nan() { value } else { x })
228 .collect(),
229 ),
230 Column::F32(v) => Column::f32(
231 v.iter()
232 .map(|&x| if x.is_nan() { value as f32 } else { x })
233 .collect(),
234 ),
235 Column::I64(v, val) => match i64::try_from_f64(value) {
236 Some(iv) => Column::i64(
237 (0..len)
238 .map(|i| if val.is_valid(i) { v[i] } else { iv })
239 .collect(),
240 ),
241 None => Column::f64(
242 (0..len)
243 .map(|i| if val.is_valid(i) { v[i] as f64 } else { value })
244 .collect(),
245 ),
246 },
247 Column::I32(v, val) => match i32::try_from_f64(value) {
248 Some(iv) => Column::i32(
249 (0..len)
250 .map(|i| if val.is_valid(i) { v[i] } else { iv })
251 .collect(),
252 ),
253 None => Column::f64(
254 (0..len)
255 .map(|i| if val.is_valid(i) { v[i] as f64 } else { value })
256 .collect(),
257 ),
258 },
259 Column::Bool(v, val) if value == 0.0 || value == 1.0 => Column::bool(
262 (0..len)
263 .map(|i| if val.is_valid(i) { v[i] } else { value != 0.0 })
264 .collect(),
265 ),
266 Column::Bool(..) => Column::f64(
267 self.to_f64_vec()
268 .iter()
269 .map(|&x| if x.is_nan() { value } else { x })
270 .collect(),
271 ),
272 Column::Str(..) | Column::Datetime(..) => {
277 return Err(VolasError::DType(format!(
278 "cannot fill a {} column with the numeric value {value} (volas has \
279 no object dtype); drop or select the missing rows instead",
280 self.dtype(),
281 )))
282 }
283 })
284 }
285
286 pub fn fill_dir(&self, forward: bool) -> Column {
290 if self.null_count() == 0 {
291 return self.clone();
292 }
293 let len = self.len();
294 let mut src = vec![None; len];
296 let mut last: Option<usize> = None;
297 for k in 0..len {
298 let i = if forward { k } else { len - 1 - k };
299 if self.is_valid(i) {
300 last = Some(i);
301 }
302 src[i] = last;
303 }
304 let validity = Validity::from_valid_iter(len, src.iter().map(|s| s.is_some()));
305 match self {
306 Column::F64(v) => {
307 Column::f64(src.iter().map(|s| s.map_or(f64::NAN, |j| v[j])).collect())
308 }
309 Column::F32(v) => {
310 Column::f32(src.iter().map(|s| s.map_or(f32::NAN, |j| v[j])).collect())
311 }
312 Column::I64(v, _) => Column::i64_with(
313 src.iter().map(|s| s.map_or(0, |j| v[j])).collect(),
314 validity,
315 ),
316 Column::I32(v, _) => Column::i32_with(
317 src.iter().map(|s| s.map_or(0, |j| v[j])).collect(),
318 validity,
319 ),
320 Column::Bool(v, _) => Column::bool_with(
321 src.iter().map(|s| s.is_some_and(|j| v[j])).collect(),
322 validity,
323 ),
324 Column::Datetime(v) => {
325 Column::datetime(src.iter().map(|s| s.map_or(i64::MIN, |j| v[j])).collect())
326 }
327 Column::Str(v, _) => Column::Str(
330 src.iter().map(|s| s.map_or("", |j| v.get(j))).collect(),
331 validity,
332 ),
333 }
334 }
335
336 pub fn select(&self, cond: &[bool], other: &Column, target: DType) -> Result<Column> {
341 match target {
342 DType::I64 => Ok(Column::i64_with(
343 stats::select(cond, &self.as_i64_vec()?, &other.as_i64_vec()?),
344 self.select_nulls(cond, other),
345 )),
346 DType::I32 => Ok(Column::i32_with(
347 stats::select(cond, &self.as_i32_vec()?, &other.as_i32_vec()?),
348 self.select_nulls(cond, other),
349 )),
350 DType::F32 => Ok(Column::f32(stats::select(
351 cond,
352 &self.to_f32_vec(),
353 &other.to_f32_vec(),
354 ))),
355 DType::Bool => {
358 let (a, b) = (self.as_bool_vec()?, other.as_bool_vec()?);
359 Ok(Column::bool_with(
360 (0..cond.len())
361 .map(|i| if cond[i] { a[i] } else { b[i] })
362 .collect(),
363 self.select_nulls(cond, other),
364 ))
365 }
366 DType::Utf8 => {
370 let (a, b) = (self.as_str_vec()?, other.as_str_vec()?);
371 Ok(Column::str_with(
372 (0..cond.len())
373 .map(|i| if cond[i] { a[i].clone() } else { b[i].clone() })
374 .collect(),
375 self.select_nulls(cond, other),
376 ))
377 }
378 DType::Datetime => {
379 let (a, b) = (self.as_datetime_vec()?, other.as_datetime_vec()?);
380 Ok(Column::datetime(
381 (0..cond.len())
382 .map(|i| if cond[i] { a[i] } else { b[i] })
383 .collect(),
384 ))
385 }
386 _ => Ok(Column::f64(stats::select(
387 cond,
388 &self.to_f64_vec(),
389 &other.to_f64_vec(),
390 ))),
391 }
392 }
393
394 fn select_nulls(&self, cond: &[bool], other: &Column) -> Validity {
397 Validity::from_valid_iter(
398 cond.len(),
399 (0..cond.len()).map(|i| {
400 if cond[i] {
401 self.is_valid(i)
402 } else {
403 other.is_valid(i)
404 }
405 }),
406 )
407 }
408}