polars_core/chunked_array/
random.rs1use rand::prelude::*;
2use rand::seq::index::IndexVec;
3use rand_distr::Uniform;
4
5use crate::prelude::DataType::Float64;
6use crate::prelude::*;
7use crate::random::get_global_random_u64;
8use crate::utils::NoNull;
9
10fn create_rand_index_with_replacement(
11 n: usize,
12 len: usize,
13 seed: Option<u64>,
14 shuffle: Option<bool>,
15) -> IdxCa {
16 if len == 0 {
17 return IdxCa::new_vec(PlSmallStr::EMPTY, vec![]);
18 }
19 let mut rng = SmallRng::seed_from_u64(seed.unwrap_or_else(get_global_random_u64));
20 let dist = Uniform::new(0, len as IdxSize).unwrap();
21 let idxs = (0..n as IdxSize)
22 .map(move |_| dist.sample(&mut rng))
23 .collect_trusted::<NoNull<IdxCa>>()
24 .into_inner();
25 if shuffle == Some(false) {
26 idxs.sort(false)
27 } else {
28 idxs
29 }
30}
31
32fn create_rand_index_no_replacement(
33 n: usize,
34 len: usize,
35 seed: Option<u64>,
36 shuffle: Option<bool>,
37) -> IdxCa {
38 let mut rng = SmallRng::seed_from_u64(seed.unwrap_or_else(get_global_random_u64));
39 let mut buf: Vec<IdxSize>;
40 if n == len {
41 buf = (0..len as IdxSize).collect();
42 if let Some(true) = shuffle {
45 buf.shuffle(&mut rng);
46 }
47 } else {
48 buf = match rand::seq::index::sample(&mut rng, len, n) {
53 IndexVec::U32(v) => v.into_iter().map(|x| x as IdxSize).collect(),
54 #[cfg(target_pointer_width = "64")]
55 IndexVec::U64(v) => v.into_iter().map(|x| x as IdxSize).collect(),
56 };
57 if let Some(false) = shuffle {
60 buf.sort_unstable();
61 }
62 }
63 IdxCa::new_vec(PlSmallStr::EMPTY, buf)
64}
65
66fn ensure_shape(n: usize, len: usize, with_replacement: bool) -> PolarsResult<()> {
67 polars_ensure!(
68 with_replacement || n <= len,
69 ShapeMismatch:
70 "cannot take a larger sample than the total population when `with_replacement=false`"
71 );
72 Ok(())
73}
74
75impl Series {
76 pub fn sample_n(
77 &self,
78 n: usize,
79 with_replacement: bool,
80 shuffle: Option<bool>,
81 seed: Option<u64>,
82 ) -> PolarsResult<Self> {
83 ensure_shape(n, self.len(), with_replacement)?;
84 if n == 0 {
85 return Ok(self.clear());
86 }
87 let len = self.len();
88
89 match with_replacement {
90 true => {
91 let idx = create_rand_index_with_replacement(n, len, seed, shuffle);
92 debug_assert_eq!(len, self.len());
93 unsafe { Ok(self.take_unchecked(&idx)) }
95 },
96 false => {
97 let idx = create_rand_index_no_replacement(n, len, seed, shuffle);
98 debug_assert_eq!(len, self.len());
99 unsafe { Ok(self.take_unchecked(&idx)) }
101 },
102 }
103 }
104
105 pub fn sample_frac(
107 &self,
108 frac: f64,
109 with_replacement: bool,
110 shuffle: Option<bool>,
111 seed: Option<u64>,
112 ) -> PolarsResult<Self> {
113 let n = (self.len() as f64 * frac) as usize;
114 self.sample_n(n, with_replacement, shuffle, seed)
115 }
116
117 pub fn shuffle(&self, seed: Option<u64>) -> Self {
118 let len = self.len();
119 let n = len;
120 let idx = create_rand_index_no_replacement(n, len, seed, Some(true));
121 debug_assert_eq!(len, self.len());
122 unsafe { self.take_unchecked(&idx) }
124 }
125}
126
127impl<T> ChunkedArray<T>
128where
129 T: PolarsDataType,
130 ChunkedArray<T>: ChunkTake<IdxCa>,
131{
132 pub fn sample_n(
134 &self,
135 n: usize,
136 with_replacement: bool,
137 shuffle: Option<bool>,
138 seed: Option<u64>,
139 ) -> PolarsResult<Self> {
140 ensure_shape(n, self.len(), with_replacement)?;
141 let len = self.len();
142
143 match with_replacement {
144 true => {
145 let idx = create_rand_index_with_replacement(n, len, seed, shuffle);
146 debug_assert_eq!(len, self.len());
147 unsafe { Ok(self.take_unchecked(&idx)) }
149 },
150 false => {
151 let idx = create_rand_index_no_replacement(n, len, seed, shuffle);
152 debug_assert_eq!(len, self.len());
153 unsafe { Ok(self.take_unchecked(&idx)) }
155 },
156 }
157 }
158
159 pub fn sample_frac(
161 &self,
162 frac: f64,
163 with_replacement: bool,
164 shuffle: Option<bool>,
165 seed: Option<u64>,
166 ) -> PolarsResult<Self> {
167 let n = (self.len() as f64 * frac) as usize;
168 self.sample_n(n, with_replacement, shuffle, seed)
169 }
170}
171
172impl DataFrame {
173 pub fn sample_n(
175 &self,
176 n: &Series,
177 with_replacement: bool,
178 shuffle: Option<bool>,
179 seed: Option<u64>,
180 ) -> PolarsResult<Self> {
181 polars_ensure!(
182 n.len() == 1,
183 ComputeError: "Sample size must be a single value."
184 );
185
186 let n = n.strict_cast(&IDX_DTYPE)?;
187 let n = n.idx()?;
188
189 match n.get(0) {
190 Some(n) => self.sample_n_literal(n as usize, with_replacement, shuffle, seed),
191 None => Ok(self.clear()),
192 }
193 }
194
195 pub fn sample_n_literal(
196 &self,
197 n: usize,
198 with_replacement: bool,
199 shuffle: Option<bool>,
200 seed: Option<u64>,
201 ) -> PolarsResult<Self> {
202 ensure_shape(n, self.height(), with_replacement)?;
203 let idx = match with_replacement {
205 true => create_rand_index_with_replacement(n, self.height(), seed, shuffle),
206 false => create_rand_index_no_replacement(n, self.height(), seed, shuffle),
207 };
208 Ok(unsafe { self.take_unchecked(&idx) })
210 }
211
212 pub fn sample_frac(
214 &self,
215 frac: &Series,
216 with_replacement: bool,
217 shuffle: Option<bool>,
218 seed: Option<u64>,
219 ) -> PolarsResult<Self> {
220 polars_ensure!(
221 frac.len() == 1,
222 ComputeError: "Sample fraction must be a single value."
223 );
224
225 let frac = frac.cast(&Float64)?;
226 let frac = frac.f64()?;
227
228 match frac.get(0) {
229 Some(frac) => {
230 let n = (self.height() as f64 * frac) as usize;
231 self.sample_n_literal(n, with_replacement, shuffle, seed)
232 },
233 None => Ok(self.clear()),
234 }
235 }
236}
237
238#[cfg(test)]
239mod test {
240 use super::*;
241
242 #[test]
243 fn test_sample() {
244 let df = df![
245 "foo" => &[1, 2, 3, 4, 5]
246 ]
247 .unwrap();
248
249 assert!(
251 df.sample_n(
252 &Series::new(PlSmallStr::from_static("s"), &[3]),
253 false,
254 None,
255 None
256 )
257 .is_ok()
258 );
259 assert!(
260 df.sample_frac(
261 &Series::new(PlSmallStr::from_static("frac"), &[0.4]),
262 false,
263 None,
264 None
265 )
266 .is_ok()
267 );
268 assert!(
270 df.sample_n(
271 &Series::new(PlSmallStr::from_static("s"), &[3]),
272 false,
273 None,
274 Some(0)
275 )
276 .is_ok()
277 );
278 assert!(
279 df.sample_frac(
280 &Series::new(PlSmallStr::from_static("frac"), &[0.4]),
281 false,
282 None,
283 Some(0)
284 )
285 .is_ok()
286 );
287 assert!(
289 df.sample_frac(
290 &Series::new(PlSmallStr::from_static("frac"), &[2.0]),
291 false,
292 None,
293 Some(0)
294 )
295 .is_err()
296 );
297 assert!(
298 df.sample_n(
299 &Series::new(PlSmallStr::from_static("s"), &[3]),
300 true,
301 None,
302 Some(0)
303 )
304 .is_ok()
305 );
306 assert!(
307 df.sample_frac(
308 &Series::new(PlSmallStr::from_static("frac"), &[0.4]),
309 true,
310 None,
311 Some(0)
312 )
313 .is_ok()
314 );
315 assert!(
317 df.sample_frac(
318 &Series::new(PlSmallStr::from_static("frac"), &[2.0]),
319 true,
320 None,
321 Some(0)
322 )
323 .is_ok()
324 );
325 }
326}