1use ndarray::IntoDimension;
2use num_traits::{Float, NumCast};
3use numpy::npyffi::flags;
4use numpy::{Element, PyArray1};
5use polars_core::prelude::*;
6use pyo3::exceptions::PyRuntimeError;
7use pyo3::prelude::*;
8use pyo3::{IntoPyObjectExt, intern};
9
10use super::to_numpy_df::df_to_numpy;
11use super::utils::{
12 create_borrowed_np_array, dtype_supports_view, polars_dtype_to_np_temporal_dtype,
13 reshape_numpy_array, series_contains_null,
14};
15use crate::conversion::ObjectValue;
16use crate::conversion::chunked_array::{decimal_to_pyobject_iter, time_to_pyobject_iter};
17use crate::series::PySeries;
18
19#[pymethods]
20impl PySeries {
21 fn to_numpy(&self, py: Python<'_>, writable: bool, allow_copy: bool) -> PyResult<PyObject> {
27 series_to_numpy(py, &self.series, writable, allow_copy)
28 }
29
30 fn to_numpy_view(&self, py: Python) -> Option<PyObject> {
36 let (view, _) = try_series_to_numpy_view(py, &self.series, true, false)?;
37 Some(view)
38 }
39}
40
41pub(super) fn series_to_numpy(
43 py: Python<'_>,
44 s: &Series,
45 writable: bool,
46 allow_copy: bool,
47) -> PyResult<PyObject> {
48 if s.is_empty() {
49 return Ok(series_to_numpy_with_copy(py, s, true));
52 }
53 if let Some((mut arr, writable_flag)) = try_series_to_numpy_view(py, s, false, allow_copy) {
54 if writable && !writable_flag {
55 if !allow_copy {
56 return Err(PyRuntimeError::new_err(
57 "copy not allowed: cannot create a writable array without copying data",
58 ));
59 }
60 arr = arr.call_method0(py, intern!(py, "copy"))?;
61 }
62 return Ok(arr);
63 }
64
65 if !allow_copy {
66 return Err(PyRuntimeError::new_err(
67 "copy not allowed: cannot convert to a NumPy array without copying data",
68 ));
69 }
70
71 Ok(series_to_numpy_with_copy(py, s, writable))
72}
73
74fn try_series_to_numpy_view(
76 py: Python<'_>,
77 s: &Series,
78 allow_nulls: bool,
79 allow_rechunk: bool,
80) -> Option<(PyObject, bool)> {
81 if !dtype_supports_view(s.dtype()) {
82 return None;
83 }
84 if !allow_nulls && series_contains_null(s) {
85 return None;
86 }
87 let (s_owned, writable_flag) = handle_chunks(py, s, allow_rechunk)?;
88 let array = series_to_numpy_view_recursive(py, s_owned, writable_flag);
89 Some((array, writable_flag))
90}
91
92fn handle_chunks(py: Python<'_>, s: &Series, allow_rechunk: bool) -> Option<(Series, bool)> {
97 let is_chunked = s.n_chunks() > 1;
98 match (is_chunked, allow_rechunk) {
99 (true, false) => None,
100 (true, true) => Some((py.allow_threads(|| s.rechunk()), true)),
101 (false, _) => Some((s.clone(), false)),
102 }
103}
104
105fn series_to_numpy_view_recursive(py: Python<'_>, s: Series, writable: bool) -> PyObject {
107 debug_assert!(s.n_chunks() == 1);
108 match s.dtype() {
109 dt if dt.is_primitive_numeric() => numeric_series_to_numpy_view(py, s, writable),
110 DataType::Datetime(_, _) | DataType::Duration(_) => {
111 temporal_series_to_numpy_view(py, s, writable)
112 },
113 DataType::Array(_, _) => array_series_to_numpy_view(py, &s, writable),
114 _ => panic!("invalid data type"),
115 }
116}
117
118fn numeric_series_to_numpy_view(py: Python<'_>, s: Series, writable: bool) -> PyObject {
120 let dims = [s.len()].into_dimension();
121 with_match_physical_numpy_polars_type!(s.dtype(), |$T| {
122 let np_dtype = <$T as PolarsNumericType>::Native::get_dtype(py);
123 let ca: &ChunkedArray<$T> = s.unpack::<$T>().unwrap();
124 let flags = if writable {
125 flags::NPY_ARRAY_FARRAY
126 } else {
127 flags::NPY_ARRAY_FARRAY_RO
128 };
129
130 let slice = ca.data_views().next().unwrap();
131
132 unsafe {
133 create_borrowed_np_array::<_>(
134 py,
135 np_dtype,
136 dims,
137 flags,
138 slice.as_ptr() as _,
139 PySeries::from(s).into_py_any(py).unwrap(), )
141 }
142 })
143}
144
145fn temporal_series_to_numpy_view(py: Python<'_>, s: Series, writable: bool) -> PyObject {
147 let np_dtype = polars_dtype_to_np_temporal_dtype(py, s.dtype());
148
149 let phys = s.to_physical_repr();
150 let ca = phys.i64().unwrap();
151 let slice = ca.data_views().next().unwrap();
152 let dims = [s.len()].into_dimension();
153 let flags = if writable {
154 flags::NPY_ARRAY_FARRAY
155 } else {
156 flags::NPY_ARRAY_FARRAY_RO
157 };
158
159 unsafe {
160 create_borrowed_np_array::<_>(
161 py,
162 np_dtype,
163 dims,
164 flags,
165 slice.as_ptr() as _,
166 PySeries::from(s).into_py_any(py).unwrap(), )
168 }
169}
170
171fn array_series_to_numpy_view(py: Python<'_>, s: &Series, writable: bool) -> PyObject {
173 let ca = s.array().unwrap();
174 let s_inner = ca.get_inner();
175 let np_array_flat = series_to_numpy_view_recursive(py, s_inner, writable);
176
177 let DataType::Array(_, width) = s.dtype() else {
179 unreachable!()
180 };
181 reshape_numpy_array(py, np_array_flat, ca.len(), *width).unwrap()
182}
183
184fn series_to_numpy_with_copy(py: Python<'_>, s: &Series, writable: bool) -> PyObject {
188 use DataType::*;
189 match s.dtype() {
190 Int8 => numeric_series_to_numpy::<Int8Type, f32>(py, s),
191 Int16 => numeric_series_to_numpy::<Int16Type, f32>(py, s),
192 Int32 => numeric_series_to_numpy::<Int32Type, f64>(py, s),
193 Int64 => numeric_series_to_numpy::<Int64Type, f64>(py, s),
194 Int128 => {
195 let s = s.cast(&DataType::Float64).unwrap();
196 series_to_numpy(py, &s, writable, true).unwrap()
197 },
198 UInt8 => numeric_series_to_numpy::<UInt8Type, f32>(py, s),
199 UInt16 => numeric_series_to_numpy::<UInt16Type, f32>(py, s),
200 UInt32 => numeric_series_to_numpy::<UInt32Type, f64>(py, s),
201 UInt64 => numeric_series_to_numpy::<UInt64Type, f64>(py, s),
202 Float32 => numeric_series_to_numpy::<Float32Type, f32>(py, s),
203 Float64 => numeric_series_to_numpy::<Float64Type, f64>(py, s),
204 Boolean => boolean_series_to_numpy(py, s),
205 Date => date_series_to_numpy(py, s),
206 Datetime(tu, _) => {
207 use numpy::datetime::{Datetime, units};
208 match tu {
209 TimeUnit::Milliseconds => {
210 temporal_series_to_numpy::<Datetime<units::Milliseconds>>(py, s)
211 },
212 TimeUnit::Microseconds => {
213 temporal_series_to_numpy::<Datetime<units::Microseconds>>(py, s)
214 },
215 TimeUnit::Nanoseconds => {
216 temporal_series_to_numpy::<Datetime<units::Nanoseconds>>(py, s)
217 },
218 }
219 },
220 Duration(tu) => {
221 use numpy::datetime::{Timedelta, units};
222 match tu {
223 TimeUnit::Milliseconds => {
224 temporal_series_to_numpy::<Timedelta<units::Milliseconds>>(py, s)
225 },
226 TimeUnit::Microseconds => {
227 temporal_series_to_numpy::<Timedelta<units::Microseconds>>(py, s)
228 },
229 TimeUnit::Nanoseconds => {
230 temporal_series_to_numpy::<Timedelta<units::Nanoseconds>>(py, s)
231 },
232 }
233 },
234 Time => {
235 let ca = s.time().unwrap();
236 let values = time_to_pyobject_iter(ca).map(|v| v.into_py_any(py).unwrap());
237 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
238 },
239 String => {
240 let ca = s.str().unwrap();
241 let values = ca.iter().map(|s| s.into_py_any(py).unwrap());
242 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
243 },
244 Binary => {
245 let ca = s.binary().unwrap();
246 let values = ca.iter().map(|s| s.into_py_any(py).unwrap());
247 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
248 },
249 Categorical(_, _) | Enum(_, _) => {
250 with_match_categorical_physical_type!(s.dtype().cat_physical().unwrap(), |$C| {
251 let ca = s.cat::<$C>().unwrap();
252 let values = ca.iter_str().map(|s| s.into_py_any(py).unwrap());
253 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
254 })
255 },
256 Decimal(_, _) => {
257 let ca = s.decimal().unwrap();
258 let values = decimal_to_pyobject_iter(py, ca)
259 .unwrap()
260 .map(|v| v.into_py_any(py).unwrap());
261 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
262 },
263 List(_) => list_series_to_numpy(py, s, writable),
264 Array(_, _) => array_series_to_numpy(py, s, writable),
265 Struct(_) => {
266 let ca = s.struct_().unwrap();
267 let df = ca.clone().unnest();
268 df_to_numpy(py, &df, IndexOrder::Fortran, writable, true).unwrap()
269 },
270 #[cfg(feature = "object")]
271 Object(_) => {
272 let ca = s
273 .as_any()
274 .downcast_ref::<ObjectChunked<ObjectValue>>()
275 .unwrap();
276 let values = ca.iter().map(|v| v.into_py_any(py).unwrap());
277 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
278 },
279 Null => {
280 let n = s.len();
281 let values = std::iter::repeat_n(f32::NAN, n);
282 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
283 },
284 Unknown(_) | BinaryOffset => unreachable!(),
285 }
286}
287
288fn numeric_series_to_numpy<T, U>(py: Python<'_>, s: &Series) -> PyObject
290where
291 T: PolarsNumericType,
292 T::Native: numpy::Element,
293 U: Float + numpy::Element,
294{
295 let ca: &ChunkedArray<T> = s.as_ref().as_ref();
296 if s.null_count() == 0 {
297 let values = ca.into_no_null_iter();
298 PyArray1::<T::Native>::from_iter(py, values)
299 .into_py_any(py)
300 .unwrap()
301 } else {
302 let mapper = |opt_v: Option<T::Native>| match opt_v {
303 Some(v) => NumCast::from(v).unwrap(),
304 None => U::nan(),
305 };
306 let values = ca.iter().map(mapper);
307 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
308 }
309}
310
311fn boolean_series_to_numpy(py: Python<'_>, s: &Series) -> PyObject {
313 let ca = s.bool().unwrap();
314 if s.null_count() == 0 {
315 let values = ca.into_no_null_iter();
316 PyArray1::<bool>::from_iter(py, values)
317 .into_py_any(py)
318 .unwrap()
319 } else {
320 let values = ca.iter().map(|opt_v| opt_v.into_py_any(py).unwrap());
321 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
322 }
323}
324
325fn date_series_to_numpy(py: Python<'_>, s: &Series) -> PyObject {
327 use numpy::datetime::{Datetime, units};
328
329 let s_phys = s.to_physical_repr();
330 let ca = s_phys.i32().unwrap();
331
332 if s.null_count() == 0 {
333 let mapper = |v: i32| (v as i64).into();
334 let values = ca.into_no_null_iter().map(mapper);
335 PyArray1::<Datetime<units::Days>>::from_iter(py, values)
336 .into_py_any(py)
337 .unwrap()
338 } else {
339 let mapper = |opt_v: Option<i32>| {
340 match opt_v {
341 Some(v) => v as i64,
342 None => i64::MIN,
343 }
344 .into()
345 };
346 let values = ca.iter().map(mapper);
347 PyArray1::<Datetime<units::Days>>::from_iter(py, values)
348 .into_py_any(py)
349 .unwrap()
350 }
351}
352
353fn temporal_series_to_numpy<T>(py: Python<'_>, s: &Series) -> PyObject
355where
356 T: From<i64> + numpy::Element,
357{
358 let s_phys = s.to_physical_repr();
359 let ca = s_phys.i64().unwrap();
360 let values = ca.iter().map(|v| v.unwrap_or(i64::MIN).into());
361 PyArray1::<T>::from_iter(py, values)
362 .into_py_any(py)
363 .unwrap()
364}
365fn list_series_to_numpy(py: Python<'_>, s: &Series, writable: bool) -> PyObject {
366 let ca = s.list().unwrap();
367
368 let iter = ca.amortized_iter().map(|opt_s| match opt_s {
369 None => py.None(),
370 Some(s) => series_to_numpy(py, s.as_ref(), writable, true).unwrap(),
371 });
372 PyArray1::from_iter(py, iter).into_py_any(py).unwrap()
373}
374
375fn array_series_to_numpy(py: Python<'_>, s: &Series, writable: bool) -> PyObject {
377 let ca = s.array().unwrap();
378 let s_inner = ca.get_inner();
379 let np_array_flat = series_to_numpy_with_copy(py, &s_inner, writable);
380
381 let DataType::Array(_, width) = s.dtype() else {
383 unreachable!()
384 };
385 reshape_numpy_array(py, np_array_flat, ca.len(), *width).unwrap()
386}