Skip to content

Commit 87acf56

Browse files
authored
fix(fractions): Speed up efficiency of fraction selection (#126)
1 parent a780511 commit 87acf56

2 files changed

Lines changed: 58 additions & 29 deletions

File tree

R/utils_fractions.R

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -264,41 +264,53 @@
264264
#' @return data.table
265265
#' @keywords internal
266266
.removeOverlappingFeatures = function(input) {
267-
fraction_keep = Fraction = NULL
267+
Fraction = NULL
268268

269269
if (data.table::uniqueN(input$Fraction) > 1) {
270-
input[, fraction_keep := .getCorrectFraction(.SD),
271-
by = "feature",
272-
.SDcols = c("feature", "Fraction", "Run", "Intensity")]
273-
input = input[Fraction == fraction_keep]
274-
input = input[, !(colnames(input) == "fraction_keep"), with = FALSE]
270+
measurement_count = input[
271+
!is.na(Intensity) & Intensity > 0,
272+
.(n_obs = uniqueN(Run)),
273+
by = .(feature, Fraction)
274+
]
275+
measurement_count[, is_max := n_obs == max(n_obs), by = "feature"]
276+
max_fractions = measurement_count[(is_max)]
277+
278+
fraction_map = .resolveFractionTies(input, max_fractions)
279+
input = input[fraction_map, on = .(feature, Fraction), nomatch = 0]
275280
}
276281
input
277282
}
278283

279284

280-
#' Get a name of fraction with the largest number of measurements or the largest
281-
#' average intensity
285+
#' Resolve ties when multiple fractions share the maximum number of measurements
286+
#' for a given feature. In the case of a tie, the fraction with the highest
287+
#' mean intensity is selected.
282288
#' @param input output of `MSstatsPreprocess`
283-
#' @return character - label of the fraction that has most measurements or
284-
#' highest mean intensity for a given feature
289+
#' @param max_fractions data.table of fractions that share the maximum number
290+
#' of unique runs per feature, as produced by `.removeOverlappingFeatures`
291+
#' @return data.table with columns `feature` and `Fraction`, containing one
292+
#' selected fraction per feature
285293
#' @keywords internal
286-
.getCorrectFraction = function(input) {
287-
Intensity = Run = Fraction = NULL
294+
.resolveFractionTies = function(input, max_fractions) {
295+
tie_features = max_fractions[, .(n_ties = .N), by = "feature"][n_ties > 1, feature]
288296

289-
measurement_count = input[!is.na(Intensity) & Intensity > 0,
290-
list(n_obs = data.table::uniqueN(Run)),
291-
by = "Fraction"]
292-
which_max_measurements = which(measurement_count$n_obs == max(measurement_count$n_obs))
293-
if (length(which_max_measurements) == 1L) {
294-
return(unique(measurement_count$Fraction[which_max_measurements]))
297+
if (length(tie_features) > 0) {
298+
tied_fractions = max_fractions[feature %in% tie_features, .(feature, Fraction)]
299+
avg_abundance = input[
300+
tied_fractions, on = .(feature, Fraction), nomatch = 0
301+
][!is.na(Intensity) & Intensity > 0,
302+
.(mean_abundance = mean(Intensity)),
303+
by = .(feature, Fraction)]
304+
best_tied = avg_abundance[, .SD[which.max(mean_abundance)], by = "feature"]
305+
best_simple = max_fractions[
306+
!feature %in% tie_features,
307+
.(Fraction = Fraction[1]),
308+
by = "feature"
309+
]
310+
rbind(best_simple[, .(feature, Fraction)],
311+
best_tied[, .(feature, Fraction)])
295312
} else {
296-
input = input[Fraction %in% measurement_count$Fraction[which_max_measurements]]
297-
average_abundance = input[!is.na(Intensity) & Intensity > 0,
298-
list(mean_abundance = mean(Intensity)),
299-
by = "Fraction"]
300-
which_max_abundance = which.max(average_abundance$mean_abundance)
301-
unique(average_abundance$Fraction[which_max_abundance])
313+
max_fractions[, .(Fraction = Fraction[1]), by = "feature"]
302314
}
303315
}
304316

inst/tinytest/test_fractions.R

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,34 @@ fractionated = data.table::data.table(
6767
Run = 1:12,
6868
Intensity = c(NA, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2)
6969
)
70-
picked_A = MSstatsConvert:::.getCorrectFraction(fractionated[feature == "A"])
71-
picked_B = MSstatsConvert:::.getCorrectFraction(fractionated[feature == "B"])
7270
### More observations win
73-
expect_equal(picked_A, 2)
74-
### Higher average intensity wins
75-
expect_equal(picked_B, 2)
71+
expect_equal(
72+
unique(MSstatsConvert:::.removeOverlappingFeatures(fractionated[feature == "A"])$Fraction),
73+
2
74+
)
75+
### Higher average intensity wins on tie
76+
expect_equal(
77+
unique(MSstatsConvert:::.removeOverlappingFeatures(fractionated[feature == "B"])$Fraction),
78+
2
79+
)
7680
### For full data
7781
expect_identical(
7882
MSstatsConvert:::.removeOverlappingFeatures(data.table::copy(fractionated)),
7983
fractionated[Fraction == 2]
8084
)
85+
### Non-tied fraction with high mean intensity should not be selected over tied fractions
86+
fractionated_third = data.table::data.table(
87+
feature = rep("A", 9),
88+
Fraction = c(rep(1, 3), rep(2, 3), rep(3, 3)),
89+
Run = 1:9,
90+
Intensity = c(1, 1, 1, # Fraction 1: 3 obs, mean = 1 (ties for max n_obs)
91+
2, 2, 2, # Fraction 2: 3 obs, mean = 2 (ties for max n_obs)
92+
10, NA, NA) # Fraction 3: 1 obs, mean = 10 (loses on n_obs but mean would win w/o fix)
93+
)
94+
expect_equal(
95+
unique(MSstatsConvert:::.removeOverlappingFeatures(fractionated_third)$Fraction),
96+
2
97+
)
8198
fractionated_tmt = fractionated = data.table::data.table(
8299
feature = rep(c("A", "B"), each = 6),
83100
Fraction = rep(rep(c(1, 2), each = 3), times = 2),

0 commit comments

Comments
 (0)