| 274 | } |
| 275 | |
| 276 | pub fn get_mutual_info( |
| 277 | x: &[f64], |
| 278 | y: &[f64], |
| 279 | n_bins: Option<usize>, |
| 280 | normalize: bool, |
| 281 | ) -> CodependenceResult<f64> { |
| 282 | if x.len() != y.len() { |
| 283 | return Err(CodependenceError::InputLengthMismatch); |
| 284 | } |
| 285 | if x.is_empty() { |
| 286 | return Err(CodependenceError::InputTooShort); |
| 287 | } |
| 288 | |
| 289 | let bins = if let Some(bins) = n_bins { |
| 290 | bins |
| 291 | } else { |
| 292 | let corr = corrcoef(x, y)?; |
| 293 | get_optimal_number_of_bins(x.len(), Some(corr))? |
| 294 | }; |
| 295 | |
| 296 | let contingency = histogram2d(x, y, bins)?; |
| 297 | let total: usize = contingency.iter().map(|row| row.iter().sum::<usize>()).sum(); |
| 298 | if total == 0 { |
| 299 | return Err(CodependenceError::InputTooShort); |
| 300 | } |
| 301 | let total_f = total as f64; |
| 302 | |
| 303 | let mut row_sums = vec![0.0; bins]; |
| 304 | let mut col_sums = vec![0.0; bins]; |
| 305 | for i in 0..bins { |
| 306 | for j in 0..bins { |
| 307 | let value = contingency[i][j] as f64; |
| 308 | row_sums[i] += value; |
| 309 | col_sums[j] += value; |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | let mut mutual_info = 0.0; |
| 314 | for i in 0..bins { |
| 315 | for j in 0..bins { |
| 316 | let value = contingency[i][j] as f64; |
| 317 | if value == 0.0 { |
| 318 | continue; |
| 319 | } |
| 320 | let p_ij = value / total_f; |
| 321 | let p_i = row_sums[i] / total_f; |
| 322 | let p_j = col_sums[j] / total_f; |
| 323 | mutual_info += p_ij * (p_ij / (p_i * p_j)).ln(); |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | if normalize { |
| 328 | let marginal_x = entropy(&histogram(x, bins)?)?; |
| 329 | let marginal_y = entropy(&histogram(y, bins)?)?; |
| 330 | let denom = marginal_x.min(marginal_y); |
| 331 | if denom == 0.0 { |
| 332 | return Err(CodependenceError::ZeroVariance); |
| 333 | } |