1use std::sync::Arc;
4
5use arrow_array::{
6 Array, ArrayRef, ArrowPrimitiveType, Float64Array, Int64Array, PrimitiveArray, RecordBatch,
7};
8use arrow_buffer::ScalarBuffer;
9use arrow_cast::cast;
10use arrow_schema::DataType;
11use rayon::prelude::*;
12use runlength::Num;
13
14use crate::batch::ArrowRleBatch;
15use crate::error::Result;
16use crate::numeric::with_numeric_type;
17use crate::rle::ArrowRle;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum SliceAgg {
23 Sum,
25 Count,
27 Mean,
29 Var { ddof: i64 },
32 Std { ddof: i64 },
34 Min,
36 Max,
38}
39
40const SLICES_PER_TASK: usize = 1024;
42
43fn check_slices(starts: &[i64], ends: &[i64]) -> Result<()> {
44 if starts.len() != ends.len() {
45 return Err(runlength::Error::LengthMismatch {
46 what: "starts and ends",
47 left: starts.len(),
48 right: ends.len(),
49 }
50 .into());
51 }
52 for (index, (&start, &end)) in starts.iter().zip(ends).enumerate() {
53 if start < 0 || end < start {
54 return Err(runlength::Error::InvalidInterval { index, start, end }.into());
55 }
56 }
57 Ok(())
58}
59
60struct Slices<'a, T: ArrowPrimitiveType, R> {
64 ends: &'a [i64],
65 values: &'a PrimitiveArray<T>,
66 starts: &'a [i64],
67 stops: &'a [i64],
68 range: R,
69}
70
71impl<T, R> Slices<'_, T, R>
72where
73 T: ArrowPrimitiveType,
74 T::Native: Num,
75 R: Fn(usize) -> Option<(usize, usize)> + Sync,
76{
77 fn runs(&self, k: usize) -> impl Iterator<Item = (i64, Option<T::Native>)> + Clone + '_ {
79 let (lo, hi) = (self.range)(k).unwrap_or((0, 0));
80 let ends = &self.ends[lo..hi];
81 let len = ends.last().copied().unwrap_or(0);
82 let (start, stop) = (self.starts[k].min(len), self.stops[k].min(len));
83 let (first, last) = if start < stop {
84 (
85 ends.partition_point(|&e| e <= start),
86 ends.partition_point(|&e| e < stop) + 1,
87 )
88 } else {
89 (0, 0)
90 };
91 (first..last).map(move |i| {
92 let run_start = if i == 0 { 0 } else { ends[i - 1] }.max(start);
93 let run_end = ends[i].min(stop);
94 let value = self
95 .values
96 .is_valid(lo + i)
97 .then(|| self.values.value(lo + i));
98 (run_end - run_start, value)
99 })
100 }
101
102 fn map<X: Send>(&self, f: impl Fn(usize) -> X + Send + Sync) -> Vec<X>
103 where
104 Self: Sync,
105 {
106 (0..self.starts.len())
107 .into_par_iter()
108 .with_min_len(SLICES_PER_TASK)
109 .map(f)
110 .collect()
111 }
112
113 fn count(&self, k: usize) -> i64 {
114 self.runs(k).filter_map(|(w, v)| v.map(|_| w)).sum::<i64>()
115 }
116
117 fn mean(&self, k: usize) -> Option<f64> {
118 let (n, total) = self.runs(k).fold((0, 0.0), |(n, total), (w, v)| match v {
119 Some(v) => (n + w, total + w as f64 * v.to_f64()),
120 None => (n, total),
121 });
122 (n > 0).then(|| total / n as f64)
123 }
124
125 fn variance(&self, k: usize, ddof: i64) -> Option<f64> {
126 let n = self.count(k);
127 if n - ddof <= 0 {
128 return None;
129 }
130 let mean = self.mean(k)?;
131 let squares: f64 = self
132 .runs(k)
133 .filter_map(|(w, v)| v.map(|v| w as f64 * (v.to_f64() - mean).powi(2)))
134 .sum();
135 Some(squares / (n - ddof) as f64)
136 }
137
138 fn extreme(&self, k: usize, max: bool) -> Option<T::Native> {
139 self.runs(k)
140 .filter_map(|(_, v)| v.filter(|v| v.partial_cmp(v).is_some()))
141 .fold(None, |best, v| match best {
142 Some(b) if (max && v <= b) || (!max && v >= b) => Some(b),
143 _ => Some(v),
144 })
145 }
146
147 fn aggregate(&self, agg: SliceAgg) -> ArrayRef
148 where
149 Self: Sync,
150 {
151 match agg {
152 SliceAgg::Sum => {
153 let sums = self.map(|k| {
154 self.runs(k)
155 .filter_map(|(w, v)| v.map(|v| (w, v)))
156 .fold(T::Native::default(), |acc, (w, v)| {
157 acc + v * T::Native::from_i64(w)
158 })
159 });
160 Arc::new(PrimitiveArray::<T>::new(ScalarBuffer::from(sums), None))
161 }
162 SliceAgg::Count => Arc::new(Int64Array::from(self.map(|k| self.count(k)))),
163 SliceAgg::Mean => Arc::new(Float64Array::from(self.map(|k| self.mean(k)))),
164 SliceAgg::Var { ddof } => {
165 Arc::new(Float64Array::from(self.map(|k| self.variance(k, ddof))))
166 }
167 SliceAgg::Std { ddof } => Arc::new(Float64Array::from(
168 self.map(|k| self.variance(k, ddof).map(f64::sqrt)),
169 )),
170 SliceAgg::Min => Arc::new(
171 self.map(|k| self.extreme(k, false))
172 .into_iter()
173 .collect::<PrimitiveArray<T>>(),
174 ),
175 SliceAgg::Max => Arc::new(
176 self.map(|k| self.extreme(k, true))
177 .into_iter()
178 .collect::<PrimitiveArray<T>>(),
179 ),
180 }
181 }
182}
183
184fn aggregate(
187 ends: &[i64],
188 values: &ArrayRef,
189 starts: &[i64],
190 stops: &[i64],
191 range: impl Fn(usize) -> Option<(usize, usize)> + Sync,
192 agg: SliceAgg,
193) -> Result<ArrayRef> {
194 check_slices(starts, stops)?;
195 if values.data_type() == &DataType::Boolean {
196 let ints = cast(values, &DataType::Int64)?;
197 let out = aggregate(ends, &ints, starts, stops, range, agg)?;
198 return match agg {
199 SliceAgg::Min | SliceAgg::Max => Ok(cast(&out, &DataType::Boolean)?),
200 _ => Ok(out),
201 };
202 }
203 with_numeric_type!(values, |T| {
204 let slices = Slices {
205 ends,
206 values: values
207 .as_any()
208 .downcast_ref::<PrimitiveArray<T>>()
209 .expect("the type was matched"),
210 starts,
211 stops,
212 range,
213 };
214 Ok(slices.aggregate(agg))
215 })
216}
217
218impl ArrowRle {
219 pub fn slice_agg(&self, starts: &[i64], ends: &[i64], agg: SliceAgg) -> Result<ArrayRef> {
234 let n = self.num_runs();
235 aggregate(
236 self.ends(),
237 self.values(),
238 starts,
239 ends,
240 |_| Some((0, n)),
241 agg,
242 )
243 }
244}
245
246impl ArrowRleBatch {
247 pub fn slice_agg(
251 &self,
252 keys: &RecordBatch,
253 starts: &[i64],
254 ends: &[i64],
255 agg: SliceAgg,
256 ) -> Result<ArrayRef> {
257 if keys.num_rows() != starts.len() {
258 return Err(runlength::Error::LengthMismatch {
259 what: "keys and starts",
260 left: keys.num_rows(),
261 right: starts.len(),
262 }
263 .into());
264 }
265 let positions = self.positions(keys)?;
266 let offsets = self.offsets();
267 aggregate(
268 self.ends(),
269 self.values(),
270 starts,
271 ends,
272 |k| positions[k].map(|p| (offsets[p], offsets[p + 1])),
273 agg,
274 )
275 }
276}