use super::*;
use crate::DType;
fn sample() -> DataFrame {
DataFrame::new(
vec!["a".into(), "b".into()],
vec![
Column::f64(vec![1.0, 2.0, 3.0]),
Column::i64(vec![10, 20, 30]),
],
None,
)
.unwrap()
}
#[test]
fn build_and_access() {
let df = sample();
assert_eq!(df.height(), 3);
assert_eq!(df.width(), 2);
assert_eq!(df.names(), &["a".to_string(), "b".to_string()]);
assert_eq!(df.column("a").unwrap().as_f64().unwrap(), &[1.0, 2.0, 3.0]);
assert!(df.column("missing").is_err());
}
#[test]
fn select_shares_index() {
let df = sample();
let sub = df.select(&["b".into()]).unwrap();
assert_eq!(sub.width(), 1);
assert_eq!(sub.height(), 3);
assert!(Arc::ptr_eq(df.index(), sub.index()));
}
#[test]
fn slice_and_filter() {
let df = sample();
let s = df.slice(1, 3);
assert_eq!(s.height(), 2);
assert_eq!(s.column("a").unwrap().as_f64().unwrap(), &[2.0, 3.0]);
let f = df.filter_mask(&[true, false, true]).unwrap();
assert_eq!(f.height(), 2);
assert_eq!(f.column("b").unwrap().as_i64().unwrap(), &[10, 30]);
}
#[test]
fn append_extends() {
let mut df = sample();
let other = sample();
df.append(&other).unwrap();
assert_eq!(df.height(), 6);
assert_eq!(
df.column("a").unwrap().as_f64().unwrap(),
&[1.0, 2.0, 3.0, 1.0, 2.0, 3.0]
);
}
#[test]
fn set_index_moves_column_out() {
let df = DataFrame::new(
vec!["t".into(), "v".into()],
vec![Column::i64(vec![100, 200]), Column::f64(vec![1.0, 2.0])],
None,
)
.unwrap();
let indexed = df.set_index("t").unwrap();
assert_eq!(indexed.names(), &["v".to_string()]);
assert_eq!(indexed.index().name(), Some("t"));
assert_eq!(
indexed.index().as_ref(),
&Index::int64(vec![100, 200]).with_name(Some("t".into()))
);
assert!(indexed.column("t").is_err());
assert!(df.set_index("v").is_err());
assert!(df.set_index("missing").is_err());
}
#[test]
fn row_major_export() {
let df = sample();
let (data, h, w) = df.to_row_major_f64();
assert_eq!((h, w), (3, 2));
assert_eq!(data, vec![1.0, 10.0, 2.0, 20.0, 3.0, 30.0]);
let na_df = DataFrame::new(
vec!["i".into(), "b".into(), "t".into()],
vec![
Column::i64_with(vec![1, 0, 3], crate::Validity::from_valid_iter(3, [true, false, true])),
Column::bool_with(vec![true, false, false], crate::Validity::from_valid_iter(3, [true, false, true])),
Column::datetime(vec![100, i64::MIN, 300]),
],
None,
)
.unwrap();
let (d2, _, _) = na_df.to_row_major_f64(); assert_eq!(d2[0], 1.0); assert!(d2[3].is_nan() && d2[4].is_nan() && d2[5].is_nan()); assert_eq!(d2[6], 3.0); }
#[test]
fn row_major_i64_export_is_exact() {
let big = (1i64 << 60) + 1;
let df = DataFrame::new(
vec!["t".into(), "n".into(), "i32".into(), "b".into(), "f".into(), "f32".into(), "s".into()],
vec![
Column::datetime(vec![123, i64::MIN]),
Column::i64(vec![big, -7]),
Column::i32(vec![5, -6]),
Column::bool(vec![true, false]),
Column::f64(vec![2.9, -2.9]),
Column::f32(vec![1.9, -1.9]),
Column::str(vec!["a".into(), "b".into()]),
],
None,
)
.unwrap();
let (data, h, w) = df.to_row_major_i64();
assert_eq!((h, w), (2, 7));
assert_eq!(&data[0..7], &[123, big, 5, 1, 2, 1, 0]);
assert_eq!(&data[7..14], &[i64::MIN, -7, -6, 0, -2, -1, 0]);
}
#[test]
fn new_validates_shape() {
assert!(DataFrame::new(vec!["a".into()], vec![], None).is_err());
assert!(DataFrame::new(
vec!["a".into(), "b".into()],
vec![Column::f64(vec![1.0, 2.0]), Column::f64(vec![1.0])],
None,
)
.is_err());
assert!(DataFrame::new(
vec!["a".into()],
vec![Column::f64(vec![1.0, 2.0])],
Some(Index::range(3)),
)
.is_err());
}
#[test]
fn series_extracts_a_named_column() {
let df = sample();
let s = df.series("a").unwrap();
assert_eq!(s.name.as_deref(), Some("a"));
assert_eq!(s.data.as_f64().unwrap(), &[1.0, 2.0, 3.0]);
assert!(Arc::ptr_eq(&s.index, df.index()));
assert!(df.series("missing").is_err());
}
#[test]
fn set_column_add_replace_and_errors() {
let mut empty = DataFrame::new(vec![], vec![], None).unwrap();
empty.set_column("x", Column::f64(vec![1.0, 2.0])).unwrap();
assert_eq!(empty.height(), 2);
assert_eq!(empty.index().as_ref(), &Index::range(2));
let mut df = sample();
df.set_column("a", Column::f64(vec![9.0, 9.0, 9.0]))
.unwrap();
assert_eq!(df.column("a").unwrap().as_f64().unwrap(), &[9.0, 9.0, 9.0]);
df.set_column("c", Column::f64(vec![7.0, 7.0, 7.0]))
.unwrap();
assert_eq!(df.width(), 3);
assert!(df.set_column("d", Column::f64(vec![1.0])).is_err());
}
#[test]
fn filter_mask_rejects_wrong_length() {
assert!(sample().filter_mask(&[true, false]).is_err());
}
#[test]
fn append_pads_missing_columns_by_dtype() {
let mut df = sample(); let only_a = DataFrame::new(vec!["a".into()], vec![Column::f64(vec![4.0])], None).unwrap();
df.append(&only_a).unwrap();
assert_eq!(df.height(), 4);
let b = df.column("b").unwrap();
assert_eq!(b.dtype(), DType::I64); assert!(b.is_valid(0) && !b.is_valid(3));
let mut g = DataFrame::new(
vec!["a".into(), "flag".into()],
vec![Column::f64(vec![1.0]), Column::bool(vec![true])],
None,
)
.unwrap();
let only_a2 = DataFrame::new(vec!["a".into()], vec![Column::f64(vec![2.0])], None).unwrap();
g.append(&only_a2).unwrap();
let flag = g.column("flag").unwrap();
assert_eq!(flag.dtype(), DType::Bool);
assert!(flag.is_valid(0) && !flag.is_valid(1));
let mut h = DataFrame::new(
vec!["a".into(), "sig".into()],
vec![Column::f64(vec![1.0]), Column::bool(vec![true])],
None,
)
.unwrap();
h.set_computed("sig", "a > 0".into(), 0);
let only_a3 = DataFrame::new(vec!["a".into()], vec![Column::f64(vec![2.0])], None).unwrap();
h.append(&only_a3).unwrap();
assert_eq!(h.column("sig").unwrap().as_bool().unwrap(), &[true, false]);
}
#[test]
fn computed_tail_update_and_dtype_guard() {
let mut df = sample();
df.set_computed("a", "ma:2".into(), 1);
assert_eq!(df.computed_columns().len(), 1);
df.update_computed_tail("a", 1, &Column::f64(vec![8.0, 9.0]))
.unwrap();
assert_eq!(df.column("a").unwrap().as_f64().unwrap(), &[1.0, 8.0, 9.0]);
assert!(df
.update_computed_tail("b", 0, &Column::f64(vec![1.0]))
.is_err());
assert!(df
.update_computed_tail("nope", 0, &Column::f64(vec![1.0]))
.is_err());
}
#[test]
fn slice_carries_computed_only_with_enough_warmup() {
let mut df = DataFrame::new(
vec!["close".into()],
vec![Column::f64((0..60).map(|i| i as f64).collect())],
None,
)
.unwrap();
df.set_computed("close", "ema:12".into(), 11);
df.set_computed_state("close", Some(vec![42.0]));
let keep = df.slice(40, 60); assert_eq!(keep.computed_columns().len(), 1);
assert_eq!(keep.computed_columns()[0].1.valid_rows, 20);
assert_eq!(keep.computed_columns()[0].1.state, Some(vec![42.0]));
let tail = df.slice(0, 50); assert_eq!(tail.computed_columns().len(), 1);
assert_eq!(tail.computed_columns()[0].1.state, None);
let too_short = df.slice(55, 60); assert!(too_short.computed_columns().is_empty());
}
#[test]
fn assign_positions_scalar_and_array() {
let mut df = sample();
df.assign_positions(0, &[0, 2], &Column::f64(vec![9.0]))
.unwrap();
assert_eq!(df.column("a").unwrap().as_f64().unwrap(), &[9.0, 2.0, 9.0]);
df.assign_positions(1, &[1, 2], &Column::f64(vec![40.0, 50.0]))
.unwrap();
assert_eq!(df.column("b").unwrap().as_i64().unwrap(), &[10, 40, 50]);
}
#[test]
fn assign_positions_fractional_into_int_errors() {
let mut df = sample();
assert!(df
.assign_positions(1, &[0], &Column::f64(vec![1.5]))
.is_err());
assert_eq!(df.column("b").unwrap().dtype(), DType::I64);
assert_eq!(df.column("b").unwrap().as_i64().unwrap(), &[10, 20, 30]);
}
#[test]
fn assign_positions_nan_into_int_keeps_int_na() {
let mut df = sample();
df.assign_positions(1, &[0], &Column::f64(vec![f64::NAN]))
.unwrap();
let b = df.column("b").unwrap();
assert_eq!(b.dtype(), DType::I64);
assert!(!b.is_valid(0) && b.is_valid(1) && b.is_valid(2));
assert_eq!(b.as_i64().unwrap()[1..], [20, 30]);
}
#[test]
fn assign_positions_drops_computed_status() {
let mut df = sample();
df.set_computed("a", "ma:2".into(), 1);
assert_eq!(df.computed_columns().len(), 1);
df.assign_positions(0, &[0], &Column::f64(vec![7.0]))
.unwrap();
assert!(df.computed_columns().is_empty());
}
#[test]
fn set_index_tz_requires_datetime_index() {
let df = DataFrame::new(
vec!["c".into()],
vec![Column::f64(vec![1.0])],
None, )
.unwrap();
assert!(df.set_index_tz(crate::tz::Tz::Utc).is_err());
}
#[test]
fn tz_convert_keeps_instant_localize_shifts() {
use crate::tz::Tz;
let ns = crate::datetime::parse_ns("2021-01-01 12:00:00").unwrap();
let df = DataFrame::new(
vec!["c".into()],
vec![Column::f64(vec![1.0])],
Some(Index::datetime(vec![ns], Tz::Utc)),
)
.unwrap();
let conv = df
.tz_convert(Tz::parse("America/New_York").unwrap())
.unwrap();
match conv.index().kind() {
IndexKind::Datetime(v, tz) => {
assert_eq!(v[0], ns);
assert_eq!(*tz, Tz::parse("America/New_York").unwrap());
}
_ => panic!("datetime"), }
assert!(df.tz_localize(Tz::parse("America/New_York").unwrap()).is_err());
let naive = DataFrame::new(
vec!["c".into()],
vec![Column::f64(vec![1.0])],
Some(Index::datetime(vec![ns], Tz::Naive)),
)
.unwrap();
assert!(naive.tz_convert(Tz::Utc).is_err());
let loc = naive
.tz_localize(Tz::parse("America/New_York").unwrap())
.unwrap();
match loc.index().kind() {
IndexKind::Datetime(v, _) => {
assert_eq!(crate::datetime::format_ns(v[0]), "2021-01-01 17:00:00");
}
_ => panic!("datetime"), }
}
#[test]
fn tz_ops_require_datetime_index() {
use crate::tz::Tz;
let df = sample(); assert!(df.tz_convert(Tz::Utc).is_err());
assert!(df.tz_localize(Tz::Utc).is_err());
}
#[test]
fn assign_positions_length_and_dtype_guards() {
let mut df = sample();
assert!(df
.assign_positions(0, &[0, 1], &Column::f64(vec![1.0, 2.0, 3.0]))
.is_err());
assert!(df
.assign_positions(0, &[9], &Column::f64(vec![1.0]))
.is_err());
df.assign_positions(0, &[0], &Column::bool(vec![true]))
.unwrap();
assert_eq!(df.column("a").unwrap().as_f64().unwrap()[0], 1.0);
assert!(df
.assign_positions(0, &[0], &Column::str(vec!["x".into()]))
.is_err());
assert!(df
.assign_positions(1, &[0], &Column::datetime(vec![123]))
.is_err());
}
#[test]
fn assign_positions_type_combinations_and_col_out_of_range() {
let mut df = DataFrame::new(
vec!["f".into(), "b".into(), "s".into(), "d".into()],
vec![
Column::f64(vec![1.0, 2.0, 3.0]),
Column::bool(vec![true, false, true]),
Column::str(vec!["a".into(), "b".into(), "c".into()]),
Column::datetime(vec![10, 20, 30]),
],
None,
)
.unwrap();
assert!(df
.assign_positions(99, &[0], &Column::f64(vec![1.0]))
.is_err());
df.assign_positions(0, &[1], &Column::i64(vec![7])).unwrap(); df.assign_positions(1, &[0], &Column::bool(vec![false]))
.unwrap(); df.assign_positions(2, &[0], &Column::str(vec!["z".into()]))
.unwrap(); df.assign_positions(3, &[2], &Column::datetime(vec![99]))
.unwrap(); assert_eq!(df.columns()[0].as_f64().unwrap()[1], 7.0);
assert_eq!(df.columns()[3].as_datetime().unwrap()[2], 99);
}
#[test]
fn tz_localize_rejects_nonexistent_wall_time() {
use crate::tz::Tz;
let ns = crate::datetime::parse_ns("2020-03-08 02:30:00").unwrap();
let df = DataFrame::new(
vec!["c".into()],
vec![Column::f64(vec![1.0])],
Some(Index::datetime(vec![ns], Tz::Utc)),
)
.unwrap();
assert!(df
.tz_localize(Tz::parse("America/New_York").unwrap())
.is_err());
}