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` (sample size: {}, population: {})",
71 n, len
72 );
73 Ok(())
74}
75
76impl Series {
77 pub fn sample_n(
78 &self,
79 n: usize,
80 with_replacement: bool,
81 shuffle: Option<bool>,
82 seed: Option<u64>,
83 ) -> PolarsResult<Self> {
84 ensure_shape(n, self.len(), with_replacement)?;
85 if n == 0 {
86 return Ok(self.clear());
87 }
88 let len = self.len();
89
90 match with_replacement {
91 true => {
92 let idx = create_rand_index_with_replacement(n, len, seed, shuffle);
93 debug_assert_eq!(len, self.len());
94 unsafe { Ok(self.take_unchecked(&idx)) }
96 },
97 false => {
98 let idx = create_rand_index_no_replacement(n, len, seed, shuffle);
99 debug_assert_eq!(len, self.len());
100 unsafe { Ok(self.take_unchecked(&idx)) }
102 },
103 }
104 }
105
106 pub fn sample_frac(
108 &self,
109 frac: f64,
110 with_replacement: bool,
111 shuffle: Option<bool>,
112 seed: Option<u64>,
113 ) -> PolarsResult<Self> {
114 let n = (self.len() as f64 * frac) as usize;
115 self.sample_n(n, with_replacement, shuffle, seed)
116 }
117
118 pub fn shuffle(&self, seed: Option<u64>) -> Self {
119 let len = self.len();
120 let n = len;
121 let idx = create_rand_index_no_replacement(n, len, seed, Some(true));
122 debug_assert_eq!(len, self.len());
123 unsafe { self.take_unchecked(&idx) }
125 }
126}
127
128impl<T> ChunkedArray<T>
129where
130 T: PolarsDataType,
131 ChunkedArray<T>: ChunkTake<IdxCa>,
132{
133 pub fn sample_n(
135 &self,
136 n: usize,
137 with_replacement: bool,
138 shuffle: Option<bool>,
139 seed: Option<u64>,
140 ) -> PolarsResult<Self> {
141 ensure_shape(n, self.len(), with_replacement)?;
142 let len = self.len();
143
144 match with_replacement {
145 true => {
146 let idx = create_rand_index_with_replacement(n, len, seed, shuffle);
147 debug_assert_eq!(len, self.len());
148 unsafe { Ok(self.take_unchecked(&idx)) }
150 },
151 false => {
152 let idx = create_rand_index_no_replacement(n, len, seed, shuffle);
153 debug_assert_eq!(len, self.len());
154 unsafe { Ok(self.take_unchecked(&idx)) }
156 },
157 }
158 }
159
160 pub fn sample_frac(
162 &self,
163 frac: f64,
164 with_replacement: bool,
165 shuffle: Option<bool>,
166 seed: Option<u64>,
167 ) -> PolarsResult<Self> {
168 let n = (self.len() as f64 * frac) as usize;
169 self.sample_n(n, with_replacement, shuffle, seed)
170 }
171}
172
173impl DataFrame {
174 pub fn sample_n(
176 &self,
177 n: &Series,
178 with_replacement: bool,
179 shuffle: Option<bool>,
180 seed: Option<u64>,
181 ) -> PolarsResult<Self> {
182 polars_ensure!(
183 n.len() == 1,
184 ComputeError: "Sample size must be a single value."
185 );
186
187 let n = n.strict_cast(&IDX_DTYPE)?;
188 let n = n.idx()?;
189
190 match n.get(0) {
191 Some(n) => self.sample_n_literal(n as usize, with_replacement, shuffle, seed),
192 None => Ok(self.clear()),
193 }
194 }
195
196 pub fn sample_n_literal(
197 &self,
198 n: usize,
199 with_replacement: bool,
200 shuffle: Option<bool>,
201 seed: Option<u64>,
202 ) -> PolarsResult<Self> {
203 ensure_shape(n, self.height(), with_replacement)?;
204 let idx = match with_replacement {
206 true => create_rand_index_with_replacement(n, self.height(), seed, shuffle),
207 false => create_rand_index_no_replacement(n, self.height(), seed, shuffle),
208 };
209 Ok(unsafe { self.take_unchecked(&idx) })
211 }
212
213 pub fn sample_frac(
215 &self,
216 frac: &Series,
217 with_replacement: bool,
218 shuffle: Option<bool>,
219 seed: Option<u64>,
220 ) -> PolarsResult<Self> {
221 polars_ensure!(
222 frac.len() == 1,
223 ComputeError: "Sample fraction must be a single value."
224 );
225
226 let frac = frac.cast(&Float64)?;
227 let frac = frac.f64()?;
228
229 match frac.get(0) {
230 Some(frac) => {
231 let n = (self.height() as f64 * frac) as usize;
232 self.sample_n_literal(n, with_replacement, shuffle, seed)
233 },
234 None => Ok(self.clear()),
235 }
236 }
237}
238
239#[cfg(test)]
240mod test {
241 use super::*;
242
243 #[test]
244 fn test_sample() {
245 let df = df![
246 "foo" => &[1, 2, 3, 4, 5]
247 ]
248 .unwrap();
249
250 assert!(
252 df.sample_n(
253 &Series::new(PlSmallStr::from_static("s"), &[3]),
254 false,
255 None,
256 None
257 )
258 .is_ok()
259 );
260 assert!(
261 df.sample_frac(
262 &Series::new(PlSmallStr::from_static("frac"), &[0.4]),
263 false,
264 None,
265 None
266 )
267 .is_ok()
268 );
269 assert!(
271 df.sample_n(
272 &Series::new(PlSmallStr::from_static("s"), &[3]),
273 false,
274 None,
275 Some(0)
276 )
277 .is_ok()
278 );
279 assert!(
280 df.sample_frac(
281 &Series::new(PlSmallStr::from_static("frac"), &[0.4]),
282 false,
283 None,
284 Some(0)
285 )
286 .is_ok()
287 );
288 assert!(
290 df.sample_frac(
291 &Series::new(PlSmallStr::from_static("frac"), &[2.0]),
292 false,
293 None,
294 Some(0)
295 )
296 .is_err()
297 );
298 assert!(
299 df.sample_n(
300 &Series::new(PlSmallStr::from_static("s"), &[3]),
301 true,
302 None,
303 Some(0)
304 )
305 .is_ok()
306 );
307 assert!(
308 df.sample_frac(
309 &Series::new(PlSmallStr::from_static("frac"), &[0.4]),
310 true,
311 None,
312 Some(0)
313 )
314 .is_ok()
315 );
316 assert!(
318 df.sample_frac(
319 &Series::new(PlSmallStr::from_static("frac"), &[2.0]),
320 true,
321 None,
322 Some(0)
323 )
324 .is_ok()
325 );
326 }
327}