Skip to main content

polars_core/frame/
dataframe.rs

1use std::borrow::Cow;
2use std::sync::{Arc, OnceLock};
3
4use polars_error::{PolarsResult, polars_bail};
5use polars_utils::broadcast::broadcast_len;
6
7use super::validation::validate_columns_slice;
8use crate::frame::column::Column;
9use crate::schema::{Schema, SchemaRef};
10
11/// A contiguous growable collection of [`Column`]s that have the same length.
12///
13/// ## Use declarations
14///
15/// All the common tools can be found in [`crate::prelude`] (or in `polars::prelude`).
16///
17/// ```rust
18/// use polars_core::prelude::*; // if the crate polars-core is used directly
19/// // use polars::prelude::*;      if the crate polars is used
20/// ```
21///
22/// # Initialization
23/// ## Default
24///
25/// A `DataFrame` can be initialized empty:
26///
27/// ```rust
28/// # use polars_core::prelude::*;
29/// let df = DataFrame::empty();
30/// assert_eq!(df.shape(), (0, 0));
31/// ```
32///
33/// ## Constructing from a `Vec<Column>`
34///
35/// A `DataFrame` is backed by a `Vec<Column>` where the `Column`s have the same length.
36/// ```rust
37/// # use polars_core::prelude::*;
38/// let s1 = Column::new("Fruit".into(), ["Apple", "Apple", "Pear"]);
39/// let s2 = Column::new("Color".into(), ["Red", "Yellow", "Green"]);
40///
41/// let df: PolarsResult<DataFrame> = DataFrame::new_infer_height(vec![s1, s2]);
42/// ```
43///
44/// ## Using a macro
45///
46/// The [`df!`] macro is a convenient method:
47///
48/// ```rust
49/// # use polars_core::prelude::*;
50/// let df: PolarsResult<DataFrame> = df!("Fruit" => ["Apple", "Apple", "Pear"],
51///                                       "Color" => ["Red", "Yellow", "Green"]);
52/// ```
53///
54/// ## Using a CSV file
55///
56/// See the `polars_io::csv::CsvReader`.
57///
58/// # Indexing
59/// ## By a number
60///
61/// The `Index<usize>` is implemented for the `DataFrame`.
62///
63/// ```rust
64/// # use polars_core::prelude::*;
65/// let df = df!("Fruit" => ["Apple", "Apple", "Pear"],
66///              "Color" => ["Red", "Yellow", "Green"])?;
67///
68/// assert_eq!(df[0], Column::new("Fruit".into(), &["Apple", "Apple", "Pear"]));
69/// assert_eq!(df[1], Column::new("Color".into(), &["Red", "Yellow", "Green"]));
70/// # Ok::<(), PolarsError>(())
71/// ```
72///
73/// ## By a `Series` name
74///
75/// ```rust
76/// # use polars_core::prelude::*;
77/// let df = df!("Fruit" => ["Apple", "Apple", "Pear"],
78///              "Color" => ["Red", "Yellow", "Green"])?;
79///
80/// assert_eq!(df["Fruit"], Column::new("Fruit".into(), &["Apple", "Apple", "Pear"]));
81/// assert_eq!(df["Color"], Column::new("Color".into(), &["Red", "Yellow", "Green"]));
82/// # Ok::<(), PolarsError>(())
83/// ```
84#[derive(Clone)]
85pub struct DataFrame {
86    height: usize,
87    /// All columns must have length equal to `self.height`.
88    columns: Vec<Column>,
89    /// Cached schema. Must be cleared if column names / dtypes in `self.columns` change.
90    cached_schema: OnceLock<SchemaRef>,
91}
92
93impl Default for DataFrame {
94    fn default() -> Self {
95        DataFrame::empty()
96    }
97}
98
99impl DataFrame {
100    /// Creates an empty `DataFrame` usable in a compile time context (such as static initializers).
101    ///
102    /// # Example
103    ///
104    /// ```rust
105    /// use polars_core::prelude::DataFrame;
106    /// static EMPTY: DataFrame = DataFrame::empty();
107    /// ```
108    pub const fn empty() -> Self {
109        DataFrame::empty_with_height(0)
110    }
111
112    pub const fn empty_with_height(height: usize) -> Self {
113        DataFrame {
114            height,
115            columns: vec![],
116            cached_schema: OnceLock::new(),
117        }
118    }
119
120    pub fn new(height: usize, columns: Vec<Column>) -> PolarsResult<Self> {
121        validate_columns_slice(height, &columns)
122            .map_err(|e| e.wrap_msg(|e| format!("could not create a new DataFrame: {e}")))?;
123
124        Ok(unsafe { DataFrame::_new_unchecked_impl(height, columns) })
125    }
126
127    /// Height is sourced from first column.
128    pub fn new_infer_height(columns: Vec<Column>) -> PolarsResult<Self> {
129        DataFrame::new(columns.first().map_or(0, |c| c.len()), columns)
130    }
131
132    /// Create a new `DataFrame` but does not check the length or duplicate occurrence of the
133    /// [`Column`]s.
134    ///
135    /// # Safety
136    /// [`Column`]s must have unique names and matching lengths.
137    pub unsafe fn new_unchecked(height: usize, columns: Vec<Column>) -> DataFrame {
138        if cfg!(debug_assertions) {
139            validate_columns_slice(height, &columns).unwrap();
140        }
141
142        unsafe { DataFrame::_new_unchecked_impl(height, columns) }
143    }
144
145    /// Height is sourced from first column. Does not check for matching height / duplicate names.
146    ///
147    /// # Safety
148    /// [`Column`]s must have unique names and matching lengths.
149    pub unsafe fn new_unchecked_infer_height(columns: Vec<Column>) -> DataFrame {
150        DataFrame::new_unchecked(columns.first().map_or(0, |c| c.len()), columns)
151    }
152
153    /// This will not panic even in debug mode - there are some (rare) use cases where a DataFrame
154    /// is temporarily constructed containing duplicates for dispatching to functions. A DataFrame
155    /// constructed with this method is generally highly unsafe and should not be long-lived.
156    #[expect(clippy::missing_safety_doc)]
157    pub const unsafe fn _new_unchecked_impl(height: usize, columns: Vec<Column>) -> DataFrame {
158        DataFrame {
159            height,
160            columns,
161            cached_schema: OnceLock::new(),
162        }
163    }
164
165    /// Broadcasts unit-length columns to `height`. Errors if a column has height that is non-unit
166    /// length and not equal to `self.height()`.
167    pub fn new_with_broadcast(height: usize, mut columns: Vec<Column>) -> PolarsResult<Self> {
168        for col in &mut columns {
169            col.broadcast_in_place_to(height)?;
170        }
171        DataFrame::new(height, columns)
172    }
173
174    /// Infers height as the first non-unit length column or 1 if not found.
175    pub fn new_infer_broadcast(columns: Vec<Column>) -> PolarsResult<Self> {
176        let height = broadcast_len(columns.iter())?;
177        DataFrame::new_with_broadcast(height, columns)
178    }
179
180    /// Broadcasts unit-length columns to `height`. Errors if a column has height that is non-unit
181    /// length and not equal to `self.height()`.
182    ///
183    /// # Safety
184    /// [`Column`]s must have unique names.
185    pub unsafe fn new_unchecked_with_broadcast(
186        height: usize,
187        mut columns: Vec<Column>,
188    ) -> PolarsResult<Self> {
189        for col in &mut columns {
190            col.broadcast_in_place_to(height)?;
191        }
192        Ok(unsafe { DataFrame::new_unchecked(height, columns) })
193    }
194
195    /// # Safety
196    /// [`Column`]s must have unique names.
197    pub unsafe fn new_unchecked_infer_broadcast(columns: Vec<Column>) -> PolarsResult<Self> {
198        let height = broadcast_len(columns.iter())?;
199        DataFrame::new_unchecked_with_broadcast(height, columns)
200    }
201
202    /// Returns a DataFrame with the given height.
203    ///
204    /// Errors if this DataFrame's height is not 1 and also not equal to the requested height.
205    pub fn broadcast_to(&self, height: usize) -> PolarsResult<Cow<'_, Self>> {
206        let len = self.height();
207        if len == height {
208            Ok(Cow::Borrowed(self))
209        } else if len == 1 {
210            Ok(Cow::Owned(self.new_from_index(0, height)))
211        } else {
212            polars_bail!(
213                ShapeMismatch: "can't broadcast DataFrame of height {len} to height {height}",
214            );
215        }
216    }
217
218    /// See broadcast_to.
219    pub fn broadcast_in_place_to(&mut self, length: usize) -> PolarsResult<()> {
220        if let Cow::Owned(new) = self.broadcast_to(length)? {
221            *self = new;
222        }
223        Ok(())
224    }
225
226    /// See broadcast_to.
227    pub fn broadcast_owned_to(mut self, length: usize) -> PolarsResult<Self> {
228        self.broadcast_in_place_to(length)?;
229        Ok(self)
230    }
231
232    /// Create a `DataFrame` 0 height and columns as per the `schema`.
233    pub fn empty_with_schema(schema: &Schema) -> Self {
234        let cols = schema
235            .iter()
236            .map(|(name, dtype)| Column::new_empty(name.clone(), dtype))
237            .collect();
238
239        unsafe { DataFrame::_new_unchecked_impl(0, cols) }
240    }
241
242    /// Create an empty `DataFrame` with empty columns as per the `schema`.
243    pub fn empty_with_arc_schema(schema: SchemaRef) -> Self {
244        let mut df = DataFrame::empty_with_schema(&schema);
245        unsafe { df.set_schema(schema) };
246        df
247    }
248
249    /// Set the height (i.e. number of rows) of this [`DataFrame`].
250    ///
251    /// # Safety
252    ///
253    /// This needs to be equal to the length of all the columns, or `self.width()` must be 0.
254    #[inline]
255    pub unsafe fn set_height(&mut self, height: usize) -> &mut Self {
256        self.height = height;
257        self
258    }
259
260    /// Get the height of the [`DataFrame`] which is the number of rows.
261    #[inline]
262    pub fn height(&self) -> usize {
263        self.height
264    }
265
266    /// Get the number of columns in this [`DataFrame`].
267    #[inline]
268    pub fn width(&self) -> usize {
269        self.columns.len()
270    }
271
272    /// Get (height, width) of the [`DataFrame`].
273    ///
274    /// # Example
275    ///
276    /// ```rust
277    /// # use polars_core::prelude::*;
278    /// let df0: DataFrame = DataFrame::empty();
279    /// let df1: DataFrame = df!("1" => [1, 2, 3, 4, 5])?;
280    /// let df2: DataFrame = df!("1" => [1, 2, 3, 4, 5],
281    ///                          "2" => [1, 2, 3, 4, 5])?;
282    ///
283    /// assert_eq!(df0.shape(), (0 ,0));
284    /// assert_eq!(df1.shape(), (5, 1));
285    /// assert_eq!(df2.shape(), (5, 2));
286    /// # Ok::<(), PolarsError>(())
287    /// ```
288    #[inline]
289    pub fn shape(&self) -> (usize, usize) {
290        (self.height(), self.width())
291    }
292
293    /// 0 width or height.
294    #[inline]
295    pub fn shape_has_zero(&self) -> bool {
296        matches!(self.shape(), (0, _) | (_, 0))
297    }
298
299    #[inline]
300    pub fn columns(&self) -> &[Column] {
301        self.columns.as_slice()
302    }
303
304    #[inline]
305    pub fn into_columns(self) -> Vec<Column> {
306        self.columns
307    }
308
309    /// # Safety
310    ///
311    /// The caller must ensure the length of all [`Column`]s remains equal to `self.height`, or
312    /// that [`DataFrame::set_height`] is called afterwards with the new `height`.
313    #[inline]
314    pub unsafe fn columns_mut(&mut self) -> &mut Vec<Column> {
315        self.clear_schema();
316        &mut self.columns
317    }
318
319    /// # Safety
320    /// Adheres to all safety requirements of [`DataFrame::columns_mut`], and that the list of column
321    /// names remains unchanged.
322    #[inline]
323    pub unsafe fn columns_mut_retain_schema(&mut self) -> &mut Vec<Column> {
324        &mut self.columns
325    }
326
327    /// Get the schema of this [`DataFrame`].
328    ///
329    /// # Panics
330    /// Panics if there are duplicate column names.
331    pub fn schema(&self) -> &SchemaRef {
332        let out = self.cached_schema.get_or_init(|| {
333            Arc::new(
334                Schema::from_iter_check_duplicates(
335                    self.columns
336                        .iter()
337                        .map(|x| (x.name().clone(), x.dtype().clone())),
338                )
339                .unwrap(),
340            )
341        });
342
343        assert_eq!(out.len(), self.width());
344
345        out
346    }
347
348    #[inline]
349    pub fn cached_schema(&self) -> Option<&SchemaRef> {
350        self.cached_schema.get()
351    }
352
353    /// Set the cached schema
354    ///
355    /// # Safety
356    /// Schema must match the columns in `self`.
357    #[inline]
358    pub unsafe fn set_schema(&mut self, schema: SchemaRef) -> &mut Self {
359        self.cached_schema = schema.into();
360        self
361    }
362
363    /// Set the cached schema
364    ///
365    /// # Safety
366    /// Schema must match the columns in `self`.
367    #[inline]
368    pub unsafe fn with_schema(mut self, schema: SchemaRef) -> Self {
369        self.cached_schema = schema.into();
370        self
371    }
372
373    /// Set the cached schema if `schema` is `Some()`.
374    ///
375    /// # Safety
376    /// Schema must match the columns in `self`.
377    #[inline]
378    pub unsafe fn set_opt_schema(&mut self, schema: Option<SchemaRef>) -> &mut Self {
379        if let Some(schema) = schema {
380            unsafe { self.set_schema(schema) };
381        }
382
383        self
384    }
385
386    /// Clones the cached schema from `from` to `self.cached_schema` if there is one.
387    ///
388    /// # Safety
389    /// Schema must match the columns in `self`.
390    #[inline]
391    pub unsafe fn set_schema_from(&mut self, from: &DataFrame) -> &mut Self {
392        self.set_opt_schema(from.cached_schema().cloned());
393        self
394    }
395
396    /// Clones the cached schema from `from` to `self.cached_schema` if there is one.
397    ///
398    /// # Safety
399    /// Schema must match the columns in `self`.
400    #[inline]
401    pub unsafe fn with_schema_from(mut self, from: &DataFrame) -> Self {
402        self.set_opt_schema(from.cached_schema().cloned());
403        self
404    }
405
406    #[inline]
407    fn clear_schema(&mut self) -> &mut Self {
408        self.cached_schema = OnceLock::new();
409        self
410    }
411}