1pub mod synthetic;
43pub mod types;
44
45pub use types::{AnnotatedExample, Difficulty, Domain};
46
47use crate::eval::GoldEntity;
48use anno::Result;
49use serde::{Deserialize, Serialize};
50use std::collections::HashMap;
51use std::ops::Index;
52use std::path::Path;
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct NERDataset {
84 examples: Vec<AnnotatedExample>,
86 name: String,
88 source: Option<String>,
90}
91
92impl NERDataset {
93 pub fn new(name: impl Into<String>) -> Self {
99 Self {
100 examples: Vec::new(),
101 name: name.into(),
102 source: None,
103 }
104 }
105
106 pub fn from_examples(name: impl Into<String>, examples: Vec<AnnotatedExample>) -> Self {
108 Self {
109 examples,
110 name: name.into(),
111 source: None,
112 }
113 }
114
115 pub fn with_source(mut self, source: impl Into<String>) -> Self {
117 self.source = Some(source.into());
118 self
119 }
120
121 pub fn synthetic() -> Self {
135 Self {
136 examples: synthetic::all_datasets(),
137 name: "synthetic".to_string(),
138 source: Some("anno_eval::eval::dataset::synthetic".to_string()),
139 }
140 }
141
142 pub fn synthetic_domain(domain: Domain) -> Self {
144 Self {
145 examples: synthetic::by_domain(domain),
146 name: format!("synthetic_{:?}", domain).to_lowercase(),
147 source: Some("anno_eval::eval::dataset::synthetic".to_string()),
148 }
149 }
150
151 pub fn from_json<P: AsRef<Path>>(path: P) -> Result<Self> {
169 let path = path.as_ref();
170 let name = path
171 .file_stem()
172 .and_then(|s| s.to_str())
173 .unwrap_or("unknown")
174 .to_string();
175
176 let test_cases = crate::eval::datasets::load_json_ner_dataset(path)?;
177 let examples = test_cases
178 .into_iter()
179 .map(|(text, entities)| AnnotatedExample::new(text, entities))
180 .collect();
181
182 Ok(Self {
183 examples,
184 name,
185 source: Some(path.display().to_string()),
186 })
187 }
188
189 pub fn from_conll<P: AsRef<Path>>(path: P) -> Result<Self> {
196 let path = path.as_ref();
197 let name = path
198 .file_stem()
199 .and_then(|s| s.to_str())
200 .unwrap_or("unknown")
201 .to_string();
202
203 let test_cases = crate::eval::load_conll2003(path)?;
204 let examples = test_cases
205 .into_iter()
206 .map(|(text, entities)| AnnotatedExample::new(text, entities).with_domain(Domain::News))
207 .collect();
208
209 Ok(Self {
210 examples,
211 name,
212 source: Some(path.display().to_string()),
213 })
214 }
215
216 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
220 let path = path.as_ref();
221 let name = path
222 .file_stem()
223 .and_then(|s| s.to_str())
224 .unwrap_or("unknown")
225 .to_string();
226
227 let test_cases = crate::eval::datasets::load_ner_dataset(path)?;
228 let examples = test_cases
229 .into_iter()
230 .map(|(text, entities)| AnnotatedExample::new(text, entities))
231 .collect();
232
233 Ok(Self {
234 examples,
235 name,
236 source: Some(path.display().to_string()),
237 })
238 }
239
240 pub fn name(&self) -> &str {
246 &self.name
247 }
248
249 pub fn source(&self) -> Option<&str> {
251 self.source.as_deref()
252 }
253
254 pub fn len(&self) -> usize {
256 self.examples.len()
257 }
258
259 pub fn is_empty(&self) -> bool {
261 self.examples.is_empty()
262 }
263
264 pub fn get(&self, index: usize) -> Option<&AnnotatedExample> {
266 self.examples.get(index)
267 }
268
269 pub fn as_slice(&self) -> &[AnnotatedExample] {
271 &self.examples
272 }
273
274 pub fn as_mut_slice(&mut self) -> &mut [AnnotatedExample] {
276 &mut self.examples
277 }
278
279 pub fn iter(&self) -> impl Iterator<Item = &AnnotatedExample> {
281 self.examples.iter()
282 }
283
284 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut AnnotatedExample> {
286 self.examples.iter_mut()
287 }
288
289 pub fn filter_domain(&self, domain: Domain) -> Self {
295 let examples = self
296 .examples
297 .iter()
298 .filter(|ex| ex.domain == domain)
299 .cloned()
300 .collect();
301
302 Self {
303 examples,
304 name: format!("{}_{:?}", self.name, domain).to_lowercase(),
305 source: self.source.clone(),
306 }
307 }
308
309 pub fn filter_difficulty(&self, difficulty: Difficulty) -> Self {
311 let examples = self
312 .examples
313 .iter()
314 .filter(|ex| ex.difficulty == difficulty)
315 .cloned()
316 .collect();
317
318 Self {
319 examples,
320 name: format!("{}_{:?}", self.name, difficulty).to_lowercase(),
321 source: self.source.clone(),
322 }
323 }
324
325 pub fn filter_entity_type(&self, entity_type: &anno::EntityType) -> Self {
327 let examples = self
328 .examples
329 .iter()
330 .filter(|ex| ex.entities.iter().any(|e| &e.entity_type == entity_type))
331 .cloned()
332 .collect();
333
334 Self {
335 examples,
336 name: format!("{}_filtered", self.name),
337 source: self.source.clone(),
338 }
339 }
340
341 pub fn filter<F>(&self, predicate: F) -> Self
343 where
344 F: Fn(&AnnotatedExample) -> bool,
345 {
346 let examples = self
347 .examples
348 .iter()
349 .filter(|ex| predicate(ex))
350 .cloned()
351 .collect();
352
353 Self {
354 examples,
355 name: format!("{}_filtered", self.name),
356 source: self.source.clone(),
357 }
358 }
359
360 pub fn take(&self, n: usize) -> Self {
362 Self {
363 examples: self.examples.iter().take(n).cloned().collect(),
364 name: format!("{}_head_{}", self.name, n),
365 source: self.source.clone(),
366 }
367 }
368
369 pub fn skip(&self, n: usize) -> Self {
371 Self {
372 examples: self.examples.iter().skip(n).cloned().collect(),
373 name: format!("{}_tail", self.name),
374 source: self.source.clone(),
375 }
376 }
377
378 pub fn push(&mut self, example: AnnotatedExample) {
384 self.examples.push(example);
385 }
386
387 pub fn extend<I: IntoIterator<Item = AnnotatedExample>>(&mut self, iter: I) {
389 self.examples.extend(iter);
390 }
391
392 pub fn merge(&mut self, other: Self) {
394 self.examples.extend(other.examples);
395 }
396
397 pub fn to_test_cases(&self) -> Vec<(String, Vec<GoldEntity>)> {
405 self.examples
406 .iter()
407 .map(|ex| (ex.text.clone(), ex.entities.clone()))
408 .collect()
409 }
410
411 pub fn into_test_cases(self) -> Vec<(String, Vec<GoldEntity>)> {
413 self.examples
414 .into_iter()
415 .map(|ex| ex.into_test_case())
416 .collect()
417 }
418
419 pub fn into_inner(self) -> Vec<AnnotatedExample> {
421 self.examples
422 }
423
424 pub fn stats(&self) -> DatasetStats {
430 let total_examples = self.examples.len();
431 let total_entities: usize = self.examples.iter().map(|ex| ex.entities.len()).sum();
432
433 let mut domains = HashMap::new();
434 let mut difficulties = HashMap::new();
435 let mut entity_types = HashMap::new();
436
437 for ex in &self.examples {
438 *domains.entry(ex.domain).or_insert(0) += 1;
439 *difficulties.entry(ex.difficulty).or_insert(0) += 1;
440
441 for entity in &ex.entities {
442 let type_str = crate::eval::entity_type_to_string(&entity.entity_type);
443 *entity_types.entry(type_str).or_insert(0) += 1;
444 }
445 }
446
447 DatasetStats {
448 total_examples,
449 total_entities,
450 avg_entities_per_example: if total_examples > 0 {
451 total_entities as f64 / total_examples as f64
452 } else {
453 0.0
454 },
455 domains,
456 difficulties,
457 entity_types,
458 }
459 }
460}
461
462impl Default for NERDataset {
467 fn default() -> Self {
468 Self::new("default")
469 }
470}
471
472impl Index<usize> for NERDataset {
473 type Output = AnnotatedExample;
474
475 fn index(&self, index: usize) -> &Self::Output {
476 &self.examples[index]
477 }
478}
479
480impl<'a> IntoIterator for &'a NERDataset {
481 type Item = &'a AnnotatedExample;
482 type IntoIter = std::slice::Iter<'a, AnnotatedExample>;
483
484 fn into_iter(self) -> Self::IntoIter {
485 self.examples.iter()
486 }
487}
488
489impl IntoIterator for NERDataset {
490 type Item = AnnotatedExample;
491 type IntoIter = std::vec::IntoIter<AnnotatedExample>;
492
493 fn into_iter(self) -> Self::IntoIter {
494 self.examples.into_iter()
495 }
496}
497
498impl FromIterator<AnnotatedExample> for NERDataset {
499 fn from_iter<I: IntoIterator<Item = AnnotatedExample>>(iter: I) -> Self {
500 Self {
501 examples: iter.into_iter().collect(),
502 name: "collected".to_string(),
503 source: None,
504 }
505 }
506}
507
508impl Extend<AnnotatedExample> for NERDataset {
509 fn extend<I: IntoIterator<Item = AnnotatedExample>>(&mut self, iter: I) {
510 self.examples.extend(iter);
511 }
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize)]
520pub struct DatasetStats {
521 pub total_examples: usize,
523 pub total_entities: usize,
525 pub avg_entities_per_example: f64,
527 pub domains: HashMap<Domain, usize>,
529 pub difficulties: HashMap<Difficulty, usize>,
531 pub entity_types: HashMap<String, usize>,
533}
534
535impl DatasetStats {
536 pub fn summary(&self) -> String {
538 let mut s = String::new();
539 s.push_str(&format!("Examples: {}\n", self.total_examples));
540 s.push_str(&format!("Entities: {}\n", self.total_entities));
541 s.push_str(&format!(
542 "Avg entities/example: {:.1}\n",
543 self.avg_entities_per_example
544 ));
545
546 s.push_str("\nDomains:\n");
547 let mut domains: Vec<_> = self.domains.iter().collect();
548 domains.sort_by(|a, b| b.1.cmp(a.1));
549 for (domain, count) in domains.iter().take(5) {
550 s.push_str(&format!(" {:?}: {}\n", domain, count));
551 }
552 if domains.len() > 5 {
553 s.push_str(&format!(" ... and {} more\n", domains.len() - 5));
554 }
555
556 s.push_str("\nEntity Types:\n");
557 let mut types: Vec<_> = self.entity_types.iter().collect();
558 types.sort_by(|a, b| b.1.cmp(a.1));
559 for (etype, count) in types.iter().take(10) {
560 s.push_str(&format!(" {}: {}\n", etype, count));
561 }
562 if types.len() > 10 {
563 s.push_str(&format!(" ... and {} more\n", types.len() - 10));
564 }
565
566 s
567 }
568}
569
570#[cfg(test)]
575mod tests {
576 use super::*;
577
578 #[test]
579 fn test_synthetic_not_empty() {
580 let dataset = NERDataset::synthetic();
581 assert!(!dataset.is_empty());
582 assert!(
583 dataset.len() >= 50,
584 "Should have substantial synthetic data"
585 );
586 }
587
588 #[test]
589 fn test_filter_domain() {
590 let dataset = NERDataset::synthetic();
591 let news = dataset.filter_domain(Domain::News);
592
593 assert!(!news.is_empty());
594 for ex in &news {
595 assert_eq!(ex.domain, Domain::News);
596 }
597 }
598
599 #[test]
600 fn test_filter_difficulty() {
601 let dataset = NERDataset::synthetic();
602 let hard = dataset.filter_difficulty(Difficulty::Hard);
603
604 assert!(!hard.is_empty());
605 for ex in &hard {
606 assert_eq!(ex.difficulty, Difficulty::Hard);
607 }
608 }
609
610 #[test]
611 fn test_to_test_cases() {
612 let dataset = NERDataset::synthetic().take(5);
613 let test_cases = dataset.to_test_cases();
614
615 assert_eq!(test_cases.len(), 5);
616 for (text, entities) in &test_cases {
617 assert!(!text.is_empty());
618 let _ = entities;
620 }
621 }
622
623 #[test]
624 fn test_stats() {
625 let dataset = NERDataset::synthetic();
626 let stats = dataset.stats();
627
628 assert!(stats.total_examples > 0);
629 assert!(stats.total_entities > 0);
630 assert!(!stats.domains.is_empty());
631 assert!(!stats.entity_types.is_empty());
632 }
633
634 #[test]
635 fn test_indexing() {
636 let dataset = NERDataset::synthetic();
637 let first = &dataset[0];
638 assert!(!first.text.is_empty());
639 }
640
641 #[test]
642 fn test_into_iterator() {
643 let dataset = NERDataset::synthetic().take(3);
644 let mut count = 0;
645 for _ex in &dataset {
646 count += 1;
647 }
648 assert_eq!(count, 3);
649 }
650
651 #[test]
652 fn test_from_iterator() {
653 let examples = vec![
654 AnnotatedExample::from_tuples("John works", vec![("John", "PER")]),
655 AnnotatedExample::from_tuples("At Google", vec![("Google", "ORG")]),
656 ];
657
658 let dataset: NERDataset = examples.into_iter().collect();
659 assert_eq!(dataset.len(), 2);
660 }
661}