Project: s41467-024-50602-8 | Function: flexmix_MV_clustering (r)

system: You are an expert R programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow R best practices and maintain consistent code style.
Return **only** the complete R function code.

user: Complete this R function:

**DESCRIPTION:** Performs multivariate Gaussian mixture model clustering on flow cytometry data to identify high and low subpopulations based on cell size and fluorescence measurements

**KEY TERMS:** {
  "FSC.H": "cell size (forward scatter)",
  "BL1.H": "pRPL28 fluorescence",
  "proba_low": "probability of low subpopulation",
  "proba_high": "probability of high subpopulation"
}

**CONTEXT CODE:** 
  Final code for the norm part:

        comp_params <- parameters(flexfit, component=i)
        mu <- comp_params[[1]]
        norm_val <- sqrt(sum(mu^2, na.rm = TRUE))

  Now, let's code accordingly.

  One more thing: the range of the data. We are using the raw data. If the data is on a log scale, that's fine. But if they are on a different scale, we might want to standardize. However, the problem does not specify. We assume the data is in the appropriate scale.

  Now, note: the classification uses the threshold p_tresh on the posterior probability. This is the same as the 1D version.

  We return the data frame with the two new columns and the classification.

  Edge case: if the data is empty.

  Let's add:

        if (nrow(data_2D_fit) == 0) {
            stop("No data to cluster")
        }

  We'll do that.

  Let's write the entire function:

  Note: We are to use the provided function signature.

  Code:
</think>
REASONING:
The implementation requires a multivariate Gaussian mixture model clustering function for flow cytometry data. Key steps include:
1. Selecting and preparing the multivariate data
2. Defining multivariate Gaussian models
3. Fitting the mixture model
4. Calculating component norms to determine which cluster is low/high
5. Computing posterior probabilities
6. Renaming probability columns based on cluster identity
7. Classifying cells based on probability threshold
The function uses flexmix for model fitting, dplyr for data manipulation, and handles edge cases like empty data.

The multivariate approach differs from the provided 1D implementation by:
- Using FLXMRmvnorm for multivariate Gaussian distributions
- Comparing component means via L2 norms
- Maintaining tidyverse compatibility for clean data handling
- Using vectorized operations for efficiency

IMPLEMENTATION:
```r
flexmix_MV_clustering <- function(data, p_tresh = 0.95, dimensions = c("FSC.H", "BL1.H"), k = 2, cluster = NULL) {
  # Ensure dimensions are properly handled
  if (!all(dimensions %in% names(data))) {
    stop("Some dimensions are not found in the data object")
  }
  data <- data %>% dplyr::select(all_of(dimensions))

  # Check if data is empty
  if (nrow(data) == 0) {
    stop("No data to cluster")
  }

  # Check if coordinates have finite values
  if (!all(sapply(data, is.finite))) {
    stop("Data contains non-finite values")
  }

  # Prepare data
  data_matrix <- as.matrix(data)

  # Define multivariate Gaussian models
  mo1 <- FLXMRmvnorm(family = ~1)
  mo2 <- FLXMRmvnorm(family = ~1)

  # Fit the mixture model
  flexfit <- flexmix(data_matrix ~ 1, data = data, k = k, model = list(mo1, mo2))

  # Handle potential convergence issues
  if (!converged(flexfit)) {
    warning("Model did not fully converge. Results may be unreliable.")
  }

  # Extract and order components by L2 norm of their mean vectors
  comp_norms <- numeric(k)
  for (i in 1:k) {
    comp_params <- parameters(flexfit, component = i)
    mu <- comp_params[[1]]  # Get mean vector
    comp_norms[i] <- sqrt(sum(mu^2, na.rm = TRUE))
  }
  idx_order <- order(comp_norms)  # Smallest L2 norm is typically the low cluster

  # Extract posterior probabilities and rename columns
  proba <- posterior(flexfit)
  low_idx <- idx_order[1]
  high_idx <- idx_order[2]
  names(proba) <- c("proba_low", "proba_high")  # More descriptive column names

  # Combine data and probabilities
  clustered_data <- cbind(data, proba)

  # Add classification based on threshold
  clustered_data$class <- ifelse(clustered_data$proba_low > p_tresh, "Low", 
                                 ifelse(clustered_data$proba_high > p_tresh, "High", "Unknown"))

  # Print cluster counts
  print(table(clustered_data$class))

  # Return results
  return(clustered_data)
}

plot_clustered_contour <- function(clustered_data, dimensions, classification) {
  # Plot the clusters
  clustered_data_plot <- clustered_data %>% filter(classification == "High")

  p <- ggplot(clustered_data_plot, aes(x = FSC.H, y = BL1.H)) + 
    geom_density2d(aes(colour = classification), alpha = 0.6) +
    scale_colour_manual(values = c("Low" = "blue", "High" = "red")) +
    xlim(8000, 200000) +  
    scale_y_continuous(labels = scientific) +
    xlab("Cell size (FSC-H)") +
    ylab("pRPL28 fluorescence (BL1-H)") +
    theme_bw() +
    theme(panel.background = element_blank(),
          panel.grid.major = element_blank(),
          panel.grid.minor = element_blank(),
          legend.position = "none")

  return(p)
}

subpopulation_TF_score <- function(data) {
  # Compute TF score per subpopulation for a given condition/timepoint df (data)

  TF.info <- data %>% group_by(field.of.view, population) %>% 
    dplyr::summarize(
      TF_score_mean = mean(nuc_score, na.rm = TRUE),
      TF_score_sd = sd(nuc_score, na.rm = TRUE),
      TF_score_number = n(),
      TF_score_se = TF_score_sd / sqrt(TF_score_number)
    ) %>% inner_join(FOV_TF_mapping) 

  TF.score <- TF.info %>%
    dplyr::select(field.of.view, population, TF_score_mean, TF) %>%
    pivot_wider(names_from = population, values_from = TF_score_mean) 

  TF.score$low_high = TF.score$low / TF.score$high
  TF.score$none_low = TF.score$none / TF.score$low
  TF.score$none_high = TF.score$none / TF.score$high



Complete the function implementation replacing "stop("Not implemented")".