Цикл данных для установки значений> или <переменная как NA в R - PullRequest
3 голосов
/ 24 сентября 2019

У меня есть фрейм данных, содержащий столбцы с целыми числами, символами и цифрами.Фактический набор данных намного больше, чем пример, приведенный ниже, но то, что ниже, является сносной и намного меньшей имитацией.

Я пытаюсь перебрать данные и изменить любые значения, превышающие mean + (3 * standard deviation) именьше чем mean - (3 * standard deviation) до NA в числовых столбцах только .Если столбец содержит целое число или символ, цикл должен пропустить его и перейти к следующему столбцу.Кроме того, большинство столбцов уже содержат некоторые значения NA и будут иметь множество значений, попадающих в mean +/- (3*sd).Эти ценности должны оставаться такими, какие они есть.

Конечная цель этого сценария - использовать его в будущих наборах данных с такой же структурой, и, хотя я открыт для предложений с пакетами, я хотел бы использовать циклы, если это возможно.Тем не менее, я далеко не эксперт в R и с удовольствием приму любой совет для меня!

Я разработал структуру для всего сценария, но он останавливается после первого оператора next.

Сценарий:

data = data.frame(test_data)

for (i in colnames(data)){
  if (class(data$i) == "numeric"){
    m = mean(data$i, na.rm=TRUE)
    sd = sd(data$i, na.rm=TRUE)
  }
    else
      next
  for (j in 1:nrow(data)){
    if (data$i[j,] > (m + 3*sd)){
      data$i[j,] <- NA
    }
    else if (data$i[j,] < (m - 3*sd)){
      data$i[j,] <- NA
    }
    else 
      next
    }
}

Используемые данныеЧтобы протестировать этот скрипт, выполните следующие действия:

Trait1 = c(1.1, 1.2, 1.35, 1.1, 1.2, NA, 1000, 1.5, 1.4, 1.6)
Trait2 = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
Trait3 = c(125.1, 119.3, 118.4, NA, 1.1, 122.3, 123.4, 125.7, 121.5, 121.7)
test_data = data.frame(Trait1, Trait2, Trait3)

Заранее благодарю за любую помощь, которую вы можете предложить, я очень благодарен вам за это!

Ответы [ 4 ]

4 голосов
/ 24 сентября 2019

Используя dplyr и преобразовывая числовые переменные в z-показатель, используя scale(), это можно упростить до:

library(dplyr)

test_data %>% 
  mutate_if(is.numeric, ~replace(.x, abs(scale(.x)) > 3, NA))
2 голосов
/ 24 сентября 2019

Вот решение без любого цикла (извините :)) с использованием функции map_df из purrr пакета:

library(purrr)

Trait1 = c(1.1, 1.2, 1.35, 1.1, 1.2, NA, 1000, 1.5, 1.4, 1.6)
Trait2 = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
Trait3 = c(125.1, 119.3, 118.4, NA, 1.1, 122.3, 123.4, 125.7, 121.5, 121.7)
test_data = data.frame(Trait1, Trait2, Trait3)

map_df(test_data,function(x) {
  if(class(x) == "numeric"){
    x[x <= (mean(x,na.rm = T) - 3*sd(x,na.rm = T)) | x>= (mean(x,na.rm = T) + 3*sd(x,na.rm = T))] = NA      
  }
  return(x)
}
)

Если вы хотите, чтобы mean и sd вычисление должно быть с NA, изменить na.rm = T на na.rm = F.

Примечание: Обратите внимание на то, что в этом случае у вас нет значения больше илименьше среднего минус или плюс три стандартных отклонения.Если вы думали, что 1000 в столбце Trait1 было вашей «подозрительной» точкой, то подумайте еще раз, поскольку она не больше, чем mean +3*sd.Я рекомендую провести тестирование на другом наборе данных.

1 голос
/ 24 сентября 2019

Если вам нужно использовать цикл, должно работать следующее:

for (i in colnames(data)){
  if (class(data[,i]) == "numeric"){
    m = mean(data[,i], na.rm=TRUE)
    sd = sd(data[,i], na.rm=TRUE)
    for (j in 1:nrow(data)){
      if (is.na(data[j,i])==F&(data[j,i] > (m + 3*sd)|data[j,i] < (m - 3*sd))){
        data[j,i] <- NA
      }
    }
  }
}

Это в основном просто упрощенная версия того, что вы написали, но основные отличия заключаются в том, что 1) написание data$i где i - строка, указывающая имя столбца, не работает и 2) если вы не укажете, что вам нужно data[j,], чтобы не быть NA, то вы можете получить ошибку при попытке запустить такие вещи, какdata[j,i] > (m + 3*sd).Другой пункт, который является более стилистическим, заключается в том, что вам не нужно обязательно включать все операторы else.В частности, вы можете просто включить оператор for(j in...) непосредственно в предложение if(class...=="numeric"), без else next, потому что else next просто заставляет его не запускать остальные, если class!="numeric", но вы уже указали, что class - это "numeric", поэтому вам не нужно указывать это снова.Надеюсь, что это имеет смысл и полезно.

0 голосов
/ 24 сентября 2019

Для такого рода вещей я использовал base::ifelse() в сочетании с tidyverse :

library(tidyverse)
library(magrittr)
library(tidylog)

test_data %<>%

  # Mutate any variable if (and only if) it's numeric...
  mutate_if(is.numeric,

            # ...then, if it meets the following criteria...
            ~ ifelse(
              test = .x > mean(.x, na.rm = TRUE) + 3 * sd(.x, na.rm = TRUE) |
                     .x < mean(.x, na.rm = TRUE) - 3 * sd(.x, na.rm = TRUE) |
                     .x %>% is.na,

              # ...replace with NA. If it doesn't...
              yes = NA,

              # ...leave as is!
              no  = .x

            ))

Обратите внимание на лямбда-функцию выше, используя ~ и .x.

Повторяя сказанное Виталием выше, этот код ничего не изменил в фиктивных данных.Чтобы быть абсолютно уверенным, я загрузил в tidylog, который представляет собой аккуратный пакет, который печатает изменения данных в кадре из-за функций tidyverse при каждом их запуске.

Редактировать: спасибо Виталию за указаниеисходный код не подлежал обобщению.Я также удалил много пуха.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...