1use super::common::*;
4use numpy::{PyArray2, PyReadonlyArray1};
5use scirs2_core::ndarray::Array1;
6use sklears_metrics::basic_metrics::{
7 accuracy_score as skl_accuracy, confusion_matrix as skl_confusion_matrix, f1_score as skl_f1,
8 precision_score as skl_precision, recall_score as skl_recall,
9};
10use std::collections::HashMap;
11
12#[pyfunction]
14#[pyo3(signature = (y_true, y_pred, normalize=true, sample_weight=None))]
15pub fn accuracy_score(
16 y_true: PyReadonlyArray1<i32>,
17 y_pred: PyReadonlyArray1<i32>,
18 normalize: bool,
19 sample_weight: Option<PyReadonlyArray1<f64>>,
20) -> PyResult<f64> {
21 let _ = sample_weight;
22 let yt = Array1::from_vec(y_true.as_array().to_vec());
23 let yp = Array1::from_vec(y_pred.as_array().to_vec());
24
25 validate_int_arrays_same_length(yt.as_slice().unwrap(), yp.as_slice().unwrap())?;
26
27 match skl_accuracy(&yt, &yp) {
28 Ok(acc) => Ok(if normalize {
29 acc
30 } else {
31 acc * yt.len() as f64
32 }),
33 Err(e) => Err(PyValueError::new_err(format!("accuracy_score: {}", e))),
34 }
35}
36
37#[pyfunction]
39#[pyo3(signature = (y_true, y_pred, labels=None, pos_label=1, average="binary", sample_weight=None, zero_division="warn"))]
40pub fn precision_score(
41 y_true: PyReadonlyArray1<i32>,
42 y_pred: PyReadonlyArray1<i32>,
43 labels: Option<PyReadonlyArray1<i32>>,
44 pos_label: i32,
45 average: &str,
46 sample_weight: Option<PyReadonlyArray1<f64>>,
47 zero_division: &str,
48) -> PyResult<f64> {
49 let _ = (labels, average, sample_weight, zero_division);
50 let yt = Array1::from_vec(y_true.as_array().to_vec());
51 let yp = Array1::from_vec(y_pred.as_array().to_vec());
52
53 validate_int_arrays_same_length(yt.as_slice().unwrap(), yp.as_slice().unwrap())?;
54
55 match skl_precision(&yt, &yp, Some(pos_label)) {
56 Ok(v) => Ok(v),
57 Err(e) => Err(PyValueError::new_err(format!("precision_score: {}", e))),
58 }
59}
60
61#[pyfunction]
63#[pyo3(signature = (y_true, y_pred, labels=None, pos_label=1, average="binary", sample_weight=None, zero_division="warn"))]
64pub fn recall_score(
65 y_true: PyReadonlyArray1<i32>,
66 y_pred: PyReadonlyArray1<i32>,
67 labels: Option<PyReadonlyArray1<i32>>,
68 pos_label: i32,
69 average: &str,
70 sample_weight: Option<PyReadonlyArray1<f64>>,
71 zero_division: &str,
72) -> PyResult<f64> {
73 let _ = (labels, average, sample_weight, zero_division);
74 let yt = Array1::from_vec(y_true.as_array().to_vec());
75 let yp = Array1::from_vec(y_pred.as_array().to_vec());
76
77 validate_int_arrays_same_length(yt.as_slice().unwrap(), yp.as_slice().unwrap())?;
78
79 match skl_recall(&yt, &yp, Some(pos_label)) {
80 Ok(v) => Ok(v),
81 Err(e) => Err(PyValueError::new_err(format!("recall_score: {}", e))),
82 }
83}
84
85#[pyfunction]
87#[pyo3(signature = (y_true, y_pred, labels=None, pos_label=1, average="binary", sample_weight=None, zero_division="warn"))]
88pub fn f1_score(
89 y_true: PyReadonlyArray1<i32>,
90 y_pred: PyReadonlyArray1<i32>,
91 labels: Option<PyReadonlyArray1<i32>>,
92 pos_label: i32,
93 average: &str,
94 sample_weight: Option<PyReadonlyArray1<f64>>,
95 zero_division: &str,
96) -> PyResult<f64> {
97 let _ = (labels, average, sample_weight, zero_division);
98 let yt = Array1::from_vec(y_true.as_array().to_vec());
99 let yp = Array1::from_vec(y_pred.as_array().to_vec());
100
101 validate_int_arrays_same_length(yt.as_slice().unwrap(), yp.as_slice().unwrap())?;
102
103 match skl_f1(&yt, &yp, Some(pos_label)) {
104 Ok(v) => Ok(v),
105 Err(e) => Err(PyValueError::new_err(format!("f1_score: {}", e))),
106 }
107}
108
109#[pyfunction]
111#[pyo3(signature = (y_true, y_pred, labels=None, sample_weight=None, normalize=None))]
112pub fn confusion_matrix(
113 py: Python,
114 y_true: PyReadonlyArray1<i32>,
115 y_pred: PyReadonlyArray1<i32>,
116 labels: Option<PyReadonlyArray1<i32>>,
117 sample_weight: Option<PyReadonlyArray1<f64>>,
118 normalize: Option<&str>,
119) -> PyResult<Py<PyArray2<i64>>> {
120 let _ = (labels, sample_weight, normalize);
121 let yt = Array1::from_vec(y_true.as_array().to_vec());
122 let yp = Array1::from_vec(y_pred.as_array().to_vec());
123
124 validate_int_arrays_same_length(yt.as_slice().unwrap(), yp.as_slice().unwrap())?;
125
126 match skl_confusion_matrix(&yt, &yp) {
127 Ok(cm) => {
128 let cm_i64 = cm.mapv(|v| v as i64);
129 Ok(PyArray2::from_array(py, &cm_i64).unbind())
130 }
131 Err(e) => Err(PyValueError::new_err(format!("confusion_matrix: {}", e))),
132 }
133}
134
135#[pyfunction]
138#[pyo3(signature = (y_true, y_pred, labels=None, target_names=None, sample_weight=None, output_dict=true))]
139pub fn classification_report(
140 y_true: PyReadonlyArray1<i32>,
141 y_pred: PyReadonlyArray1<i32>,
142 labels: Option<PyReadonlyArray1<i32>>,
143 target_names: Option<Vec<String>>,
144 sample_weight: Option<PyReadonlyArray1<f64>>,
145 output_dict: bool,
146) -> PyResult<HashMap<String, HashMap<String, f64>>> {
147 let _ = (labels, target_names, sample_weight);
148 if !output_dict {
149 return Err(PyValueError::new_err(
150 "String output not supported; use output_dict=True.",
151 ));
152 }
153
154 let yt = Array1::from_vec(y_true.as_array().to_vec());
155 let yp = Array1::from_vec(y_pred.as_array().to_vec());
156
157 validate_int_arrays_same_length(yt.as_slice().unwrap(), yp.as_slice().unwrap())?;
158
159 let pos_label = *yt.iter().max().unwrap_or(&1);
160 let support = yt.len() as f64;
161
162 let precision = skl_precision(&yt, &yp, Some(pos_label)).unwrap_or(0.0);
163 let recall = skl_recall(&yt, &yp, Some(pos_label)).unwrap_or(0.0);
164 let f1 = skl_f1(&yt, &yp, Some(pos_label)).unwrap_or(0.0);
165
166 let mut class_entry = HashMap::new();
167 class_entry.insert("precision".to_string(), precision);
168 class_entry.insert("recall".to_string(), recall);
169 class_entry.insert("f1-score".to_string(), f1);
170 class_entry.insert("support".to_string(), support);
171
172 let mut report = HashMap::new();
173 report.insert(pos_label.to_string(), class_entry);
174 Ok(report)
175}