1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! Label encoding utilities for converting between sparse integer labels and
//! one-hot (categorical) representations
//!
//! Provides `to_categorical` and `to_categorical_with_mapping` for one-hot encoding,
//! and `to_sparse_categorical` for the inverse via per-row argmax
use crateError;
use AHashMap;
use ;
/// Converts sparse categorical labels to one-hot encoded format
///
/// Takes a 1D array of integer labels and produces a 2D one-hot encoded matrix
/// where each row is a sample and each column is a class. The value is 1.0 for
/// the corresponding class and 0.0 for all other classes
///
/// # Parameters
///
/// - `labels` - A 1D array of integer labels (e.g. \[0, 1, 2, 1, 0\])
/// - `num_classes` - Optional class count; if None, inferred from the maximum label value + 1
///
/// # Returns
///
/// - `Result<Array2<f64>, Error>` - A 2D one-hot encoded matrix of shape (n_samples, n_classes)
///
/// # Examples
///
/// ```rust
/// use ndarray::array;
/// use rustyml::utils::label_encoding::to_categorical;
///
/// let labels = array![0, 1, 2, 1, 0];
/// let categorical = to_categorical(&labels, None).unwrap();
/// assert_eq!(
/// categorical,
/// array![
/// [1.0, 0.0, 0.0],
/// [0.0, 1.0, 0.0],
/// [0.0, 0.0, 1.0],
/// [0.0, 1.0, 0.0],
/// [1.0, 0.0, 0.0]
/// ]
/// );
/// ```
///
/// # Errors
///
/// - [`Error::InvalidInput`] - If any label is negative
/// - [`Error::InvalidParameter`] - If `num_classes` is smaller than the maximum label + 1
/// Converts sparse categorical labels to one-hot format with a custom label mapping
///
/// Useful when labels are non-consecutive integers or strings that need to be
/// mapped to consecutive integers first. Classes are indexed in first-seen order
///
/// # Parameters
///
/// - `labels` - A slice of labels that can be compared and hashed
/// - `num_classes` - Optional class count; if None, inferred from the number of unique labels
///
/// # Returns
///
/// - `Result<(Array2<f64>, AHashMap<T, usize>), Error>` - The one-hot encoded matrix paired with the mapping from original labels to class indices
///
/// # Examples
///
/// ```rust
/// use rustyml::utils::label_encoding::to_categorical_with_mapping;
///
/// let labels = vec!["cat", "dog", "bird", "dog", "cat"];
/// let (categorical, mapping) = to_categorical_with_mapping(&labels, None).unwrap();
/// // Classes are indexed in first-seen order
/// assert_eq!(mapping["cat"], 0);
/// assert_eq!(mapping["dog"], 1);
/// assert_eq!(mapping["bird"], 2);
/// // "cat" -> column 0, so the first row is one-hot at index 0
/// assert_eq!(categorical.row(0).to_vec(), vec![1.0, 0.0, 0.0]);
/// ```
///
/// # Errors
///
/// - [`Error::InvalidParameter`] - If `num_classes` is smaller than the number of unique labels
/// Converts one-hot encoded format back to sparse categorical labels
///
/// Inverse of `to_categorical`. Each row is reduced to the index of its highest
/// value, which turns model predictions back into class labels. Ties resolve to
/// the first (lowest) index, matching numpy/keras `argmax`
///
/// # Parameters
///
/// - `categorical` - A 2D one-hot encoded matrix where each row is a sample
///
/// # Returns
///
/// - `Result<Array1<i32>, Error>` - A 1D array of integer labels in sparse categorical format
///
/// # Examples
///
/// ```rust
/// use ndarray::array;
/// use rustyml::utils::label_encoding::to_sparse_categorical;
///
/// let categorical = array![[1.0, 0.0, 0.0],
/// [0.0, 1.0, 0.0],
/// [0.0, 0.0, 1.0]];
/// let sparse_labels = to_sparse_categorical(&categorical).unwrap();
/// assert_eq!(sparse_labels, array![0, 1, 2]);
/// ```
///
/// # Errors
///
/// - [`Error::NonFinite`] - If the input contains NaN or infinite values