@@ -8,7 +8,9 @@ use numpy::{
8
8
use polars_core:: prelude:: * ;
9
9
use polars_core:: utils:: try_get_supertype;
10
10
use polars_core:: with_match_physical_numeric_polars_type;
11
+ use pyo3:: intern;
11
12
use pyo3:: prelude:: * ;
13
+ use pyo3:: types:: PyTuple ;
12
14
13
15
use crate :: conversion:: Wrap ;
14
16
use crate :: dataframe:: PyDataFrame ;
@@ -58,59 +60,66 @@ impl PySeries {
58
60
/// appropriately.
59
61
#[ allow( clippy:: wrong_self_convention) ]
60
62
pub fn to_numpy_view ( & self , py : Python ) -> Option < PyObject > {
61
- // NumPy arrays are always contiguous
62
- if self . series . n_chunks ( ) > 1 {
63
- return None ;
64
- }
63
+ series_to_numpy_view ( py, & self . series , true )
64
+ }
65
+ }
65
66
66
- match self . series . dtype ( ) {
67
- dt if dt. is_numeric ( ) => {
68
- let dims = [ self . series . len ( ) ] . into_dimension ( ) ;
69
- let owner = self . clone ( ) . into_py ( py) ; // Keep the Series memory alive.
70
- with_match_physical_numeric_polars_type ! ( dt, |$T | {
71
- let np_dtype = <$T as PolarsNumericType >:: Native :: get_dtype_bound( py) ;
72
- let ca: & ChunkedArray <$T > = self . series. unpack:: <$T >( ) . unwrap( ) ;
73
- let slice = ca. data_views( ) . next( ) . unwrap( ) ;
74
-
75
- let view = unsafe {
76
- create_borrowed_np_array:: <_>(
77
- py,
78
- np_dtype,
79
- dims,
80
- flags:: NPY_ARRAY_FARRAY_RO ,
81
- slice. as_ptr( ) as _,
82
- owner,
83
- )
84
- } ;
85
- Some ( view)
86
- } )
87
- } ,
88
- dt @ ( DataType :: Datetime ( _, _) | DataType :: Duration ( _) ) => {
89
- let np_dtype = polars_dtype_to_np_temporal_dtype ( py, dt) ;
90
-
91
- let phys = self . series . to_physical_repr ( ) ;
92
- let ca = phys. i64 ( ) . unwrap ( ) ;
93
- let slice = ca. data_views ( ) . next ( ) . unwrap ( ) ;
94
- let dims = [ self . series . len ( ) ] . into_dimension ( ) ;
95
- let owner = self . clone ( ) . into_py ( py) ;
96
-
97
- let view = unsafe {
98
- create_borrowed_np_array :: < _ > (
99
- py,
100
- np_dtype,
101
- dims,
102
- flags:: NPY_ARRAY_FARRAY_RO ,
103
- slice. as_ptr ( ) as _ ,
104
- owner,
105
- )
106
- } ;
107
- Some ( view)
108
- } ,
109
- _ => None ,
110
- }
67
+ pub ( crate ) fn series_to_numpy_view ( py : Python , s : & Series , allow_nulls : bool ) -> Option < PyObject > {
68
+ // NumPy arrays are always contiguous
69
+ if s. n_chunks ( ) > 1 {
70
+ return None ;
71
+ }
72
+ if !allow_nulls && s. null_count ( ) > 0 {
73
+ return None ;
111
74
}
75
+ let view = match s. dtype ( ) {
76
+ dt if dt. is_numeric ( ) => numeric_series_to_numpy_view ( py, s) ,
77
+ DataType :: Datetime ( _, _) | DataType :: Duration ( _) => temporal_series_to_numpy_view ( py, s) ,
78
+ DataType :: Array ( _, _) => array_series_to_numpy_view ( py, s, allow_nulls) ?,
79
+ _ => return None ,
80
+ } ;
81
+ Some ( view)
82
+ }
83
+ fn numeric_series_to_numpy_view ( py : Python , s : & Series ) -> PyObject {
84
+ let dims = [ s. len ( ) ] . into_dimension ( ) ;
85
+ let owner = PySeries :: from ( s. clone ( ) ) . into_py ( py) ; // Keep the Series memory alive.
86
+ with_match_physical_numeric_polars_type ! ( s. dtype( ) , |$T | {
87
+ let np_dtype = <$T as PolarsNumericType >:: Native :: get_dtype_bound( py) ;
88
+ let ca: & ChunkedArray <$T > = s. unpack:: <$T >( ) . unwrap( ) ;
89
+ let slice = ca. data_views( ) . next( ) . unwrap( ) ;
90
+
91
+ unsafe {
92
+ create_borrowed_np_array:: <_>(
93
+ py,
94
+ np_dtype,
95
+ dims,
96
+ flags:: NPY_ARRAY_FARRAY_RO ,
97
+ slice. as_ptr( ) as _,
98
+ owner,
99
+ )
100
+ }
101
+ } )
112
102
}
103
+ fn temporal_series_to_numpy_view ( py : Python , s : & Series ) -> PyObject {
104
+ let np_dtype = polars_dtype_to_np_temporal_dtype ( py, s. dtype ( ) ) ;
105
+
106
+ let phys = s. to_physical_repr ( ) ;
107
+ let ca = phys. i64 ( ) . unwrap ( ) ;
108
+ let slice = ca. data_views ( ) . next ( ) . unwrap ( ) ;
109
+ let dims = [ s. len ( ) ] . into_dimension ( ) ;
110
+ let owner = PySeries :: from ( s. clone ( ) ) . into_py ( py) ; // Keep the Series memory alive.
113
111
112
+ unsafe {
113
+ create_borrowed_np_array :: < _ > (
114
+ py,
115
+ np_dtype,
116
+ dims,
117
+ flags:: NPY_ARRAY_FARRAY_RO ,
118
+ slice. as_ptr ( ) as _ ,
119
+ owner,
120
+ )
121
+ }
122
+ }
114
123
/// Get the NumPy temporal data type associated with the given Polars [`DataType`].
115
124
fn polars_dtype_to_np_temporal_dtype < ' a > (
116
125
py : Python < ' a > ,
@@ -139,6 +148,46 @@ fn polars_dtype_to_np_temporal_dtype<'a>(
139
148
_ => panic ! ( "only Datetime/Duration inputs supported, got {}" , dtype) ,
140
149
}
141
150
}
151
+ fn array_series_to_numpy_view ( py : Python , s : & Series , allow_nulls : bool ) -> Option < PyObject > {
152
+ let ca = s. array ( ) . unwrap ( ) ;
153
+ let s_inner = ca. get_inner ( ) ;
154
+ let np_array_flat = series_to_numpy_view ( py, & s_inner, allow_nulls) ?;
155
+
156
+ // Reshape to the original shape.
157
+ let DataType :: Array ( _, width) = s. dtype ( ) else {
158
+ unreachable ! ( )
159
+ } ;
160
+ let view = reshape_numpy_array ( py, np_array_flat, ca. len ( ) , * width) ;
161
+ Some ( view)
162
+ }
163
+ /// Reshape the first dimension of a NumPy array to the given height and width.
164
+ pub ( crate ) fn reshape_numpy_array (
165
+ py : Python ,
166
+ arr : PyObject ,
167
+ height : usize ,
168
+ width : usize ,
169
+ ) -> PyObject {
170
+ let shape = arr
171
+ . getattr ( py, intern ! ( py, "shape" ) )
172
+ . unwrap ( )
173
+ . extract :: < Vec < usize > > ( py)
174
+ . unwrap ( ) ;
175
+
176
+ if shape. len ( ) == 1 {
177
+ // In this case we can avoid allocating a Vec.
178
+ let new_shape = ( height, width) ;
179
+ arr. call_method1 ( py, intern ! ( py, "reshape" ) , new_shape)
180
+ . unwrap ( )
181
+ } else {
182
+ let mut new_shape_vec = vec ! [ height, width] ;
183
+ for v in & shape[ 1 ..] {
184
+ new_shape_vec. push ( * v)
185
+ }
186
+ let new_shape = PyTuple :: new_bound ( py, new_shape_vec) ;
187
+ arr. call_method1 ( py, intern ! ( py, "reshape" ) , new_shape)
188
+ . unwrap ( )
189
+ }
190
+ }
142
191
143
192
#[ pymethods]
144
193
#[ allow( clippy:: wrong_self_convention) ]
0 commit comments