miniextendr_api/dataframe/
group.rs1use std::collections::BTreeMap;
39
40use super::{DataFrame, DataFrameError, FromDataFrame};
41use crate::{SEXP, SEXPTYPE, SexpExt};
42
43pub fn group_rows<T, K, F>(rows: Vec<T>, key: F) -> BTreeMap<K, Vec<T>>
51where
52 K: Ord,
53 F: Fn(&T) -> K,
54{
55 let mut groups: BTreeMap<K, Vec<T>> = BTreeMap::new();
56 for row in rows {
57 groups.entry(key(&row)).or_default().push(row);
58 }
59 groups
60}
61#[derive(Debug, Clone, PartialEq, Eq, Hash)]
67pub enum GroupKey {
68 Str(String),
70 Int(i32),
72 Bool(bool),
74 Na,
76}
77
78impl GroupKey {
79 pub fn label(&self) -> String {
82 match self {
83 GroupKey::Str(s) => s.clone(),
84 GroupKey::Int(i) => i.to_string(),
85 GroupKey::Bool(true) => "TRUE".to_string(),
86 GroupKey::Bool(false) => "FALSE".to_string(),
87 GroupKey::Na => "NA".to_string(),
88 }
89 }
90}
91
92impl std::fmt::Display for GroupKey {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 match self {
95 GroupKey::Str(s) => f.write_str(s),
96 GroupKey::Int(i) => write!(f, "{}", i),
97 GroupKey::Bool(true) => f.write_str("TRUE"),
98 GroupKey::Bool(false) => f.write_str("FALSE"),
99 GroupKey::Na => f.write_str("NA"),
100 }
101 }
102}
103pub struct GroupedDataFrame {
124 source: DataFrame,
125 groups: Vec<(GroupKey, Vec<usize>)>,
126}
127
128impl Drop for GroupedDataFrame {
129 fn drop(&mut self) {
130 unsafe { crate::sys::R_ReleaseObject(self.source.as_sexp()) };
133 }
134}
135
136impl GroupedDataFrame {
137 pub fn len(&self) -> usize {
139 self.groups.len()
140 }
141
142 pub fn is_empty(&self) -> bool {
144 self.groups.is_empty()
145 }
146
147 pub fn source(&self) -> &DataFrame {
149 &self.source
150 }
151
152 pub fn iter(&self) -> impl Iterator<Item = (&GroupKey, &[usize])> {
155 self.groups.iter().map(|(k, idx)| (k, idx.as_slice()))
156 }
157
158 pub fn frames(&self) -> impl Iterator<Item = (&GroupKey, DataFrame)> {
166 self.groups
167 .iter()
168 .map(|(k, idx)| (k, self.source.select_rows(idx)))
169 }
170
171 pub fn extract<T>(&self) -> Result<Vec<(GroupKey, Vec<T>)>, DataFrameError>
177 where
178 Vec<T>: FromDataFrame,
179 {
180 let rows = Vec::<T>::from_dataframe(&self.source)?;
181 let mut slots: Vec<Option<T>> = rows.into_iter().map(Some).collect();
182 Ok(self
183 .groups
184 .iter()
185 .map(|(key, idx)| {
186 let group_rows: Vec<T> = idx
187 .iter()
188 .map(|&i| slots[i].take().expect("group indices are disjoint"))
189 .collect();
190 (key.clone(), group_rows)
191 })
192 .collect())
193 }
194}
195impl DataFrame {
200 pub fn group_by(&self, col: &str) -> Result<GroupedDataFrame, DataFrameError> {
210 let column = self
211 .column_raw(col)
212 .ok_or_else(|| DataFrameError::NoSuchColumn(col.to_string()))?;
213 let groups = if column.is_factor() {
214 factor_groups(column)
215 } else {
216 match column.type_of() {
217 SEXPTYPE::STRSXP => character_groups(column),
218 SEXPTYPE::INTSXP => integer_groups(column),
219 SEXPTYPE::LGLSXP => logical_groups(column),
220 other => {
221 return Err(DataFrameError::UnsupportedGroupColumn {
222 column: col.to_string(),
223 type_of: format!("{:?}", other),
224 });
225 }
226 }
227 };
228 unsafe {
232 let _guard = crate::OwnedProtect::new(self.sexp);
233 crate::sys::R_PreserveObject(self.sexp);
234 }
235 Ok(GroupedDataFrame {
236 source: *self,
237 groups,
238 })
239 }
240}
241
242fn factor_groups(column: SEXP) -> Vec<(GroupKey, Vec<usize>)> {
245 let codes: &[i32] = unsafe { column.as_slice() };
247 let levels = column.get_levels();
248 let n_levels: usize = if levels.is_nil() { 0 } else { levels.len() };
249
250 let mut buckets: Vec<Vec<usize>> = vec![Vec::new(); n_levels];
251 let mut na_bucket: Vec<usize> = Vec::new();
252 for (row, &code) in codes.iter().enumerate() {
253 if code == i32::MIN {
254 na_bucket.push(row);
255 } else {
256 buckets[(code - 1) as usize].push(row);
257 }
258 }
259
260 let mut groups: Vec<(GroupKey, Vec<usize>)> = Vec::with_capacity(n_levels + 1);
261 for (lvl, bucket) in buckets.into_iter().enumerate() {
262 let key = match levels.string_elt_str(lvl as isize) {
263 Some(label) => GroupKey::Str(label.to_string()),
264 None => GroupKey::Na, };
266 groups.push((key, bucket));
267 }
268 if !na_bucket.is_empty() {
269 groups.push((GroupKey::Na, na_bucket));
270 }
271 groups
272}
273
274fn character_groups(column: SEXP) -> Vec<(GroupKey, Vec<usize>)> {
276 let n = column.len() as isize;
277 let mut map: BTreeMap<String, Vec<usize>> = BTreeMap::new();
278 let mut na_bucket: Vec<usize> = Vec::new();
279 for i in 0..n {
280 match column.string_elt_str(i) {
281 Some(s) => map.entry(s.to_string()).or_default().push(i as usize),
282 None => na_bucket.push(i as usize),
283 }
284 }
285 let mut groups: Vec<(GroupKey, Vec<usize>)> = map
286 .into_iter()
287 .map(|(k, idx)| (GroupKey::Str(k), idx))
288 .collect();
289 if !na_bucket.is_empty() {
290 groups.push((GroupKey::Na, na_bucket));
291 }
292 groups
293}
294
295fn integer_groups(column: SEXP) -> Vec<(GroupKey, Vec<usize>)> {
297 let values: &[i32] = unsafe { column.as_slice() };
299 let mut map: BTreeMap<i32, Vec<usize>> = BTreeMap::new();
300 let mut na_bucket: Vec<usize> = Vec::new();
301 for (row, &v) in values.iter().enumerate() {
302 if v == i32::MIN {
303 na_bucket.push(row);
304 } else {
305 map.entry(v).or_default().push(row);
306 }
307 }
308 let mut groups: Vec<(GroupKey, Vec<usize>)> = map
309 .into_iter()
310 .map(|(k, idx)| (GroupKey::Int(k), idx))
311 .collect();
312 if !na_bucket.is_empty() {
313 groups.push((GroupKey::Na, na_bucket));
314 }
315 groups
316}
317
318fn logical_groups(column: SEXP) -> Vec<(GroupKey, Vec<usize>)> {
321 let n = column.len() as isize;
322 let mut false_bucket: Vec<usize> = Vec::new();
323 let mut true_bucket: Vec<usize> = Vec::new();
324 let mut na_bucket: Vec<usize> = Vec::new();
325 for i in 0..n {
326 match column.logical_elt(i) {
327 0 => false_bucket.push(i as usize),
328 v if v == i32::MIN => na_bucket.push(i as usize),
329 _ => true_bucket.push(i as usize),
330 }
331 }
332 let mut groups: Vec<(GroupKey, Vec<usize>)> = Vec::with_capacity(3);
333 if !false_bucket.is_empty() {
334 groups.push((GroupKey::Bool(false), false_bucket));
335 }
336 if !true_bucket.is_empty() {
337 groups.push((GroupKey::Bool(true), true_bucket));
338 }
339 if !na_bucket.is_empty() {
340 groups.push((GroupKey::Na, na_bucket));
341 }
342 groups
343}
344#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn group_rows_partitions_and_orders_by_key() {
352 let rows = vec![("b", 1), ("a", 2), ("b", 3), ("c", 4)];
353 let grouped = group_rows(rows, |r| r.0);
354 let keys: Vec<&str> = grouped.keys().copied().collect();
355 assert_eq!(keys, vec!["a", "b", "c"]);
356 assert_eq!(grouped["b"], vec![("b", 1), ("b", 3)]);
357 }
358
359 #[test]
360 fn group_rows_option_key_gives_na_a_home() {
361 let rows = vec![(Some(2), "x"), (None, "y"), (Some(1), "z")];
362 let grouped = group_rows(rows, |r| r.0);
363 let keys: Vec<Option<i32>> = grouped.keys().copied().collect();
364 assert_eq!(keys, vec![None, Some(1), Some(2)]);
365 }
366
367 #[test]
368 fn group_key_labels_match_r_printing() {
369 assert_eq!(GroupKey::Str("a".into()).label(), "a");
370 assert_eq!(GroupKey::Int(-3).label(), "-3");
371 assert_eq!(GroupKey::Bool(true).label(), "TRUE");
372 assert_eq!(GroupKey::Bool(false).label(), "FALSE");
373 assert_eq!(GroupKey::Na.label(), "NA");
374 assert_eq!(GroupKey::Na.to_string(), "NA");
375 }
376}