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::{intern, IntoPyObjectExt};
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::chunked_array::{decimal_to_pyobject_iter, time_to_pyobject_iter};
16use crate::conversion::ObjectValue;
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
89 let array = series_to_numpy_view_recursive(py, s_owned, writable_flag);
90 Some((array, writable_flag))
91}
92
93fn handle_chunks(py: Python, s: &Series, allow_rechunk: bool) -> Option<(Series, bool)> {
98 let is_chunked = s.n_chunks() > 1;
99 match (is_chunked, allow_rechunk) {
100 (true, false) => None,
101 (true, true) => Some((py.allow_threads(|| s.rechunk()), true)),
102 (false, _) => Some((s.clone(), false)),
103 }
104}
105
106fn series_to_numpy_view_recursive(py: Python, s: Series, writable: bool) -> PyObject {
108 debug_assert!(s.n_chunks() == 1);
109 match s.dtype() {
110 dt if dt.is_primitive_numeric() => numeric_series_to_numpy_view(py, s, writable),
111 DataType::Datetime(_, _) | DataType::Duration(_) => {
112 temporal_series_to_numpy_view(py, s, writable)
113 },
114 DataType::Array(_, _) => array_series_to_numpy_view(py, &s, writable),
115 _ => panic!("invalid data type"),
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}
144fn temporal_series_to_numpy_view(py: Python, s: Series, writable: bool) -> PyObject {
146 let np_dtype = polars_dtype_to_np_temporal_dtype(py, s.dtype());
147
148 let phys = s.to_physical_repr();
149 let ca = phys.i64().unwrap();
150 let slice = ca.data_views().next().unwrap();
151 let dims = [s.len()].into_dimension();
152 let flags = if writable {
153 flags::NPY_ARRAY_FARRAY
154 } else {
155 flags::NPY_ARRAY_FARRAY_RO
156 };
157
158 unsafe {
159 create_borrowed_np_array::<_>(
160 py,
161 np_dtype,
162 dims,
163 flags,
164 slice.as_ptr() as _,
165 PySeries::from(s).into_py_any(py).unwrap(), )
167 }
168}
169fn array_series_to_numpy_view(py: Python, s: &Series, writable: bool) -> PyObject {
171 let ca = s.array().unwrap();
172 let s_inner = ca.get_inner();
173 let np_array_flat = series_to_numpy_view_recursive(py, s_inner, writable);
174
175 let DataType::Array(_, width) = s.dtype() else {
177 unreachable!()
178 };
179 reshape_numpy_array(py, np_array_flat, ca.len(), *width).unwrap()
180}
181
182fn series_to_numpy_with_copy(py: Python, s: &Series, writable: bool) -> PyObject {
186 use DataType::*;
187 match s.dtype() {
188 Int8 => numeric_series_to_numpy::<Int8Type, f32>(py, s),
189 Int16 => numeric_series_to_numpy::<Int16Type, f32>(py, s),
190 Int32 => numeric_series_to_numpy::<Int32Type, f64>(py, s),
191 Int64 => numeric_series_to_numpy::<Int64Type, f64>(py, s),
192 Int128 => {
193 let s = s.cast(&DataType::Float64).unwrap();
194 series_to_numpy(py, &s, writable, true).unwrap()
195 },
196 UInt8 => numeric_series_to_numpy::<UInt8Type, f32>(py, s),
197 UInt16 => numeric_series_to_numpy::<UInt16Type, f32>(py, s),
198 UInt32 => numeric_series_to_numpy::<UInt32Type, f64>(py, s),
199 UInt64 => numeric_series_to_numpy::<UInt64Type, f64>(py, s),
200 Float32 => numeric_series_to_numpy::<Float32Type, f32>(py, s),
201 Float64 => numeric_series_to_numpy::<Float64Type, f64>(py, s),
202 Boolean => boolean_series_to_numpy(py, s),
203 Date => date_series_to_numpy(py, s),
204 Datetime(tu, _) => {
205 use numpy::datetime::{units, Datetime};
206 match tu {
207 TimeUnit::Milliseconds => {
208 temporal_series_to_numpy::<Datetime<units::Milliseconds>>(py, s)
209 },
210 TimeUnit::Microseconds => {
211 temporal_series_to_numpy::<Datetime<units::Microseconds>>(py, s)
212 },
213 TimeUnit::Nanoseconds => {
214 temporal_series_to_numpy::<Datetime<units::Nanoseconds>>(py, s)
215 },
216 }
217 },
218 Duration(tu) => {
219 use numpy::datetime::{units, Timedelta};
220 match tu {
221 TimeUnit::Milliseconds => {
222 temporal_series_to_numpy::<Timedelta<units::Milliseconds>>(py, s)
223 },
224 TimeUnit::Microseconds => {
225 temporal_series_to_numpy::<Timedelta<units::Microseconds>>(py, s)
226 },
227 TimeUnit::Nanoseconds => {
228 temporal_series_to_numpy::<Timedelta<units::Nanoseconds>>(py, s)
229 },
230 }
231 },
232 Time => {
233 let ca = s.time().unwrap();
234 let values = time_to_pyobject_iter(ca).map(|v| v.into_py_any(py).unwrap());
235 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
236 },
237 String => {
238 let ca = s.str().unwrap();
239 let values = ca.iter().map(|s| s.into_py_any(py).unwrap());
240 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
241 },
242 Binary => {
243 let ca = s.binary().unwrap();
244 let values = ca.iter().map(|s| s.into_py_any(py).unwrap());
245 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
246 },
247 Categorical(_, _) | Enum(_, _) => {
248 let ca = s.categorical().unwrap();
249 let values = ca.iter_str().map(|s| s.into_py_any(py).unwrap());
250 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
251 },
252 Decimal(_, _) => {
253 let ca = s.decimal().unwrap();
254 let values = decimal_to_pyobject_iter(py, ca)
255 .unwrap()
256 .map(|v| v.into_py_any(py).unwrap());
257 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
258 },
259 List(_) => list_series_to_numpy(py, s, writable),
260 Array(_, _) => array_series_to_numpy(py, s, writable),
261 Struct(_) => {
262 let ca = s.struct_().unwrap();
263 let df = ca.clone().unnest();
264 df_to_numpy(py, &df, IndexOrder::Fortran, writable, true).unwrap()
265 },
266 #[cfg(feature = "object")]
267 Object(_, _) => {
268 let ca = s
269 .as_any()
270 .downcast_ref::<ObjectChunked<ObjectValue>>()
271 .unwrap();
272 let values = ca.iter().map(|v| v.into_py_any(py).unwrap());
273 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
274 },
275 Null => {
276 let n = s.len();
277 let values = std::iter::repeat(f32::NAN).take(n);
278 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
279 },
280 Unknown(_) | BinaryOffset => unreachable!(),
281 }
282}
283
284fn numeric_series_to_numpy<T, U>(py: Python, s: &Series) -> PyObject
286where
287 T: PolarsNumericType,
288 T::Native: numpy::Element,
289 U: Float + numpy::Element,
290{
291 let ca: &ChunkedArray<T> = s.as_ref().as_ref();
292 if s.null_count() == 0 {
293 let values = ca.into_no_null_iter();
294 PyArray1::<T::Native>::from_iter(py, values)
295 .into_py_any(py)
296 .unwrap()
297 } else {
298 let mapper = |opt_v: Option<T::Native>| match opt_v {
299 Some(v) => NumCast::from(v).unwrap(),
300 None => U::nan(),
301 };
302 let values = ca.iter().map(mapper);
303 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
304 }
305}
306fn boolean_series_to_numpy(py: Python, s: &Series) -> PyObject {
308 let ca = s.bool().unwrap();
309 if s.null_count() == 0 {
310 let values = ca.into_no_null_iter();
311 PyArray1::<bool>::from_iter(py, values)
312 .into_py_any(py)
313 .unwrap()
314 } else {
315 let values = ca.iter().map(|opt_v| opt_v.into_py_any(py).unwrap());
316 PyArray1::from_iter(py, values).into_py_any(py).unwrap()
317 }
318}
319fn date_series_to_numpy(py: Python, s: &Series) -> PyObject {
321 use numpy::datetime::{units, Datetime};
322
323 let s_phys = s.to_physical_repr();
324 let ca = s_phys.i32().unwrap();
325
326 if s.null_count() == 0 {
327 let mapper = |v: i32| (v as i64).into();
328 let values = ca.into_no_null_iter().map(mapper);
329 PyArray1::<Datetime<units::Days>>::from_iter(py, values)
330 .into_py_any(py)
331 .unwrap()
332 } else {
333 let mapper = |opt_v: Option<i32>| {
334 match opt_v {
335 Some(v) => v as i64,
336 None => i64::MIN,
337 }
338 .into()
339 };
340 let values = ca.iter().map(mapper);
341 PyArray1::<Datetime<units::Days>>::from_iter(py, values)
342 .into_py_any(py)
343 .unwrap()
344 }
345}
346fn temporal_series_to_numpy<T>(py: Python, s: &Series) -> PyObject
348where
349 T: From<i64> + numpy::Element,
350{
351 let s_phys = s.to_physical_repr();
352 let ca = s_phys.i64().unwrap();
353 let values = ca.iter().map(|v| v.unwrap_or(i64::MIN).into());
354 PyArray1::<T>::from_iter(py, values)
355 .into_py_any(py)
356 .unwrap()
357}
358fn list_series_to_numpy(py: Python, s: &Series, writable: bool) -> PyObject {
359 let ca = s.list().unwrap();
360
361 let iter = ca.amortized_iter().map(|opt_s| match opt_s {
362 None => py.None(),
363 Some(s) => series_to_numpy(py, s.as_ref(), writable, true).unwrap(),
364 });
365 PyArray1::from_iter(py, iter).into_py_any(py).unwrap()
366}
367fn array_series_to_numpy(py: Python, s: &Series, writable: bool) -> PyObject {
369 let ca = s.array().unwrap();
370 let s_inner = ca.get_inner();
371 let np_array_flat = series_to_numpy_with_copy(py, &s_inner, writable);
372
373 let DataType::Array(_, width) = s.dtype() else {
375 unreachable!()
376 };
377 reshape_numpy_array(py, np_array_flat, ca.len(), *width).unwrap()
378}