1use num_traits::{Float, NumCast};
2use polars_error::to_compute_err;
3use rand::distr::Bernoulli;
4use rand::prelude::*;
5use rand::seq::index::IndexVec;
6use rand_distr::{Normal, StandardNormal, StandardUniform, Uniform};
7
8use crate::prelude::DataType::Float64;
9use crate::prelude::*;
10use crate::random::get_global_random_u64;
11use crate::utils::NoNull;
12
13fn create_rand_index_with_replacement(n: usize, len: usize, seed: Option<u64>) -> IdxCa {
14 if len == 0 {
15 return IdxCa::new_vec(PlSmallStr::EMPTY, vec![]);
16 }
17 let mut rng = SmallRng::seed_from_u64(seed.unwrap_or_else(get_global_random_u64));
18 let dist = Uniform::new(0, len as IdxSize).unwrap();
19 (0..n as IdxSize)
20 .map(move |_| dist.sample(&mut rng))
21 .collect_trusted::<NoNull<IdxCa>>()
22 .into_inner()
23}
24
25fn create_rand_index_no_replacement(
26 n: usize,
27 len: usize,
28 seed: Option<u64>,
29 shuffle: Option<bool>,
30) -> IdxCa {
31 let mut rng = SmallRng::seed_from_u64(seed.unwrap_or_else(get_global_random_u64));
32 let mut buf: Vec<IdxSize>;
33 if n == len {
34 buf = (0..len as IdxSize).collect();
35 if let Some(true) = shuffle {
38 buf.shuffle(&mut rng);
39 }
40 } else {
41 buf = match rand::seq::index::sample(&mut rng, len, n) {
46 IndexVec::U32(v) => v.into_iter().map(|x| x as IdxSize).collect(),
47 #[cfg(target_pointer_width = "64")]
48 IndexVec::U64(v) => v.into_iter().map(|x| x as IdxSize).collect(),
49 };
50 if let Some(false) = shuffle {
53 buf.sort_unstable();
54 }
55 }
56 IdxCa::new_vec(PlSmallStr::EMPTY, buf)
57}
58
59impl<T> ChunkedArray<T>
60where
61 T: PolarsNumericType,
62 StandardUniform: Distribution<T::Native>,
63{
64 pub fn init_rand(size: usize, null_density: f32, seed: Option<u64>) -> Self {
65 let mut rng = SmallRng::seed_from_u64(seed.unwrap_or_else(get_global_random_u64));
66 (0..size)
67 .map(|_| {
68 if rng.random::<f32>() < null_density {
69 None
70 } else {
71 Some(rng.random())
72 }
73 })
74 .collect()
75 }
76}
77
78fn ensure_shape(n: usize, len: usize, with_replacement: bool) -> PolarsResult<()> {
79 polars_ensure!(
80 with_replacement || n <= len,
81 ShapeMismatch:
82 "cannot take a larger sample than the total population when `with_replacement=false`"
83 );
84 Ok(())
85}
86
87impl Series {
88 pub fn sample_n(
89 &self,
90 n: usize,
91 with_replacement: bool,
92 shuffle: Option<bool>,
93 seed: Option<u64>,
94 ) -> PolarsResult<Self> {
95 ensure_shape(n, self.len(), with_replacement)?;
96 if n == 0 {
97 return Ok(self.clear());
98 }
99 let len = self.len();
100
101 match with_replacement {
102 true => {
103 let idx = create_rand_index_with_replacement(n, len, seed);
104 debug_assert_eq!(len, self.len());
105 unsafe { Ok(self.take_unchecked(&idx)) }
107 },
108 false => {
109 let idx = create_rand_index_no_replacement(n, len, seed, shuffle);
110 debug_assert_eq!(len, self.len());
111 unsafe { Ok(self.take_unchecked(&idx)) }
113 },
114 }
115 }
116
117 pub fn sample_frac(
119 &self,
120 frac: f64,
121 with_replacement: bool,
122 shuffle: Option<bool>,
123 seed: Option<u64>,
124 ) -> PolarsResult<Self> {
125 let n = (self.len() as f64 * frac) as usize;
126 self.sample_n(n, with_replacement, shuffle, seed)
127 }
128
129 pub fn shuffle(&self, seed: Option<u64>) -> Self {
130 let len = self.len();
131 let n = len;
132 let idx = create_rand_index_no_replacement(n, len, seed, Some(true));
133 debug_assert_eq!(len, self.len());
134 unsafe { self.take_unchecked(&idx) }
136 }
137}
138
139impl<T> ChunkedArray<T>
140where
141 T: PolarsDataType,
142 ChunkedArray<T>: ChunkTake<IdxCa>,
143{
144 pub fn sample_n(
146 &self,
147 n: usize,
148 with_replacement: bool,
149 shuffle: Option<bool>,
150 seed: Option<u64>,
151 ) -> PolarsResult<Self> {
152 ensure_shape(n, self.len(), with_replacement)?;
153 let len = self.len();
154
155 match with_replacement {
156 true => {
157 let idx = create_rand_index_with_replacement(n, len, seed);
158 debug_assert_eq!(len, self.len());
159 unsafe { Ok(self.take_unchecked(&idx)) }
161 },
162 false => {
163 let idx = create_rand_index_no_replacement(n, len, seed, shuffle);
164 debug_assert_eq!(len, self.len());
165 unsafe { Ok(self.take_unchecked(&idx)) }
167 },
168 }
169 }
170
171 pub fn sample_frac(
173 &self,
174 frac: f64,
175 with_replacement: bool,
176 shuffle: Option<bool>,
177 seed: Option<u64>,
178 ) -> PolarsResult<Self> {
179 let n = (self.len() as f64 * frac) as usize;
180 self.sample_n(n, with_replacement, shuffle, seed)
181 }
182}
183
184impl DataFrame {
185 pub fn sample_n(
187 &self,
188 n: &Series,
189 with_replacement: bool,
190 shuffle: Option<bool>,
191 seed: Option<u64>,
192 ) -> PolarsResult<Self> {
193 polars_ensure!(
194 n.len() == 1,
195 ComputeError: "Sample size must be a single value."
196 );
197
198 let n = n.strict_cast(&IDX_DTYPE)?;
199 let n = n.idx()?;
200
201 match n.get(0) {
202 Some(n) => self.sample_n_literal(n as usize, with_replacement, shuffle, seed),
203 None => Ok(self.clear()),
204 }
205 }
206
207 pub fn sample_n_literal(
208 &self,
209 n: usize,
210 with_replacement: bool,
211 shuffle: Option<bool>,
212 seed: Option<u64>,
213 ) -> PolarsResult<Self> {
214 ensure_shape(n, self.height(), with_replacement)?;
215 let idx = match with_replacement {
217 true => create_rand_index_with_replacement(n, self.height(), seed),
218 false => create_rand_index_no_replacement(n, self.height(), seed, shuffle),
219 };
220 Ok(unsafe { self.take_unchecked(&idx) })
222 }
223
224 pub fn sample_frac(
226 &self,
227 frac: &Series,
228 with_replacement: bool,
229 shuffle: Option<bool>,
230 seed: Option<u64>,
231 ) -> PolarsResult<Self> {
232 polars_ensure!(
233 frac.len() == 1,
234 ComputeError: "Sample fraction must be a single value."
235 );
236
237 let frac = frac.cast(&Float64)?;
238 let frac = frac.f64()?;
239
240 match frac.get(0) {
241 Some(frac) => {
242 let n = (self.height() as f64 * frac) as usize;
243 self.sample_n_literal(n, with_replacement, shuffle, seed)
244 },
245 None => Ok(self.clear()),
246 }
247 }
248}
249
250impl<T> ChunkedArray<T>
251where
252 T: PolarsNumericType,
253 T::Native: Float,
254{
255 pub fn rand_normal(
257 name: PlSmallStr,
258 length: usize,
259 mean: f64,
260 std_dev: f64,
261 ) -> PolarsResult<Self> {
262 let normal = Normal::new(mean, std_dev).map_err(to_compute_err)?;
263 let mut builder = PrimitiveChunkedBuilder::<T>::new(name, length);
264 let mut rng = rand::rng();
265 for _ in 0..length {
266 let smpl = normal.sample(&mut rng);
267 let smpl = NumCast::from(smpl).unwrap();
268 builder.append_value(smpl)
269 }
270 Ok(builder.finish())
271 }
272
273 pub fn rand_standard_normal(name: PlSmallStr, length: usize) -> Self {
275 let mut builder = PrimitiveChunkedBuilder::<T>::new(name, length);
276 let mut rng = rand::rng();
277 for _ in 0..length {
278 let smpl: f64 = rng.sample(StandardNormal);
279 let smpl = NumCast::from(smpl).unwrap();
280 builder.append_value(smpl)
281 }
282 builder.finish()
283 }
284
285 pub fn rand_uniform(name: PlSmallStr, length: usize, low: f64, high: f64) -> Self {
287 let uniform = Uniform::new(low, high).unwrap();
288 let mut builder = PrimitiveChunkedBuilder::<T>::new(name, length);
289 let mut rng = rand::rng();
290 for _ in 0..length {
291 let smpl = uniform.sample(&mut rng);
292 let smpl = NumCast::from(smpl).unwrap();
293 builder.append_value(smpl)
294 }
295 builder.finish()
296 }
297}
298
299impl BooleanChunked {
300 pub fn rand_bernoulli(name: PlSmallStr, length: usize, p: f64) -> PolarsResult<Self> {
302 let dist = Bernoulli::new(p).map_err(to_compute_err)?;
303 let mut rng = rand::rng();
304 let mut builder = BooleanChunkedBuilder::new(name, length);
305 for _ in 0..length {
306 let smpl = dist.sample(&mut rng);
307 builder.append_value(smpl)
308 }
309 Ok(builder.finish())
310 }
311}
312
313#[cfg(test)]
314mod test {
315 use super::*;
316
317 #[test]
318 fn test_sample() {
319 let df = df![
320 "foo" => &[1, 2, 3, 4, 5]
321 ]
322 .unwrap();
323
324 assert!(
326 df.sample_n(
327 &Series::new(PlSmallStr::from_static("s"), &[3]),
328 false,
329 None,
330 None
331 )
332 .is_ok()
333 );
334 assert!(
335 df.sample_frac(
336 &Series::new(PlSmallStr::from_static("frac"), &[0.4]),
337 false,
338 None,
339 None
340 )
341 .is_ok()
342 );
343 assert!(
345 df.sample_n(
346 &Series::new(PlSmallStr::from_static("s"), &[3]),
347 false,
348 None,
349 Some(0)
350 )
351 .is_ok()
352 );
353 assert!(
354 df.sample_frac(
355 &Series::new(PlSmallStr::from_static("frac"), &[0.4]),
356 false,
357 None,
358 Some(0)
359 )
360 .is_ok()
361 );
362 assert!(
364 df.sample_frac(
365 &Series::new(PlSmallStr::from_static("frac"), &[2.0]),
366 false,
367 None,
368 Some(0)
369 )
370 .is_err()
371 );
372 assert!(
373 df.sample_n(
374 &Series::new(PlSmallStr::from_static("s"), &[3]),
375 true,
376 None,
377 Some(0)
378 )
379 .is_ok()
380 );
381 assert!(
382 df.sample_frac(
383 &Series::new(PlSmallStr::from_static("frac"), &[0.4]),
384 true,
385 None,
386 Some(0)
387 )
388 .is_ok()
389 );
390 assert!(
392 df.sample_frac(
393 &Series::new(PlSmallStr::from_static("frac"), &[2.0]),
394 true,
395 None,
396 Some(0)
397 )
398 .is_ok()
399 );
400 }
401}