Замените повторяющиеся значения, используя несколько условий в r

#r

#r

Вопрос:

Я новичок в R, и у меня есть следующие данные (пример) в виде файла csv, и я хочу заменить любые повторяющиеся значения, если они возникали в последовательные дни в течение аналогичного года и месяца, на ноль или букву. Мне нужно сохранить только одно среднее значение.

 Year    Month   Day Average
2013    8       28   2.3
2013    8       29   2.3
2013    8       30   1.7
2013    8       31   1.7
2014    8       7    3
2014    8       6    3
2014    8       8    3
2014    8       9    3
2014    9       11   5.8
2014    9       12   5.8
2014    9       13   5.8
  

Результат, который я ожидаю, будет примерно таким

 Year    Month   Day Average
2013    8       28   2.3
2013    8       29   0
2013    8       30   1.7
2013    8       31   0
2014    8       7    3
2014    8       6    0
2014    8       8    0
2014    8       9    0
2014    9       11   5.8
2014    9       12   0
2014    9       13   0
  

Также я хотел бы иметь возможность удалять строки, содержащие повторяющиеся значения, которые были заменены следующим образом:

 Year    Month   Day Average
2013    8       28   2.3
2013    8       30   1.7
2014    8       7    3
2014    9       11   5.8
  

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

Заранее благодарю вас!!

Комментарии:

1. Пожалуйста, рассмотрите возможность использования dput или чего-то подобного для обмена вашими данными, это значительно упрощает помощь.

2. В течение двух последовательных дней, если данные отличаются, то это имеет смысл, но если они округляются до одного и того же числа, вы отбрасываете их? Я не знаю базовых данных, но похоже, что вы будете выбрасывать потенциально хорошие данные. Кроме того, имеет ли значение порядок? Вы сохраняете более раннее среднее значение для всех, кроме 2014/8/6.

Ответ №1:

Используя dplyr для обработки данных.frame, lubridate для обработки даты и diff для поиска последовательных повторяющихся значений.

Обратите внимание, что я также отсортировал даты, чтобы сохранить самую раннюю, что делает ее не совсем совпадающей с примером решения.

 library(dplyr)

## 
## Attaching package: 'dplyr'

## The following objects are masked from 'package:stats':
## 
##     filter, lag

## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

library(lubridate)

## 
## Attaching package: 'lubridate'

## The following object is masked from 'package:base':
## 
##     date

df1 <- read.table(
  text = "
    Year    Month   Day Average
    2013    8       28   2.3
    2013    8       29   2.3
    2013    8       30   1.7
    2013    8       31   1.7
    2014    8       7    3
    2014    8       6    3
    2014    8       8    3
    2014    8       9    3
    2014    9       11   5.8
    2014    9       12   5.8
    2014    9       13   5.8",
header = T)

df2 <- read.table(
  text = "
    Year    Month   Day Average
    2013    8       28   2.3
    2013    8       29   0
    2013    8       30   1.7
    2013    8       31   0
    2014    8       7    3
    2014    8       6    0
    2014    8       8    0
    2014    8       9    0
    2014    9       11   5.8
    2014    9       12   0
    2014    9       13   0",
header = T)

df3 <- read.table(
  text = "
    Year    Month   Day Average
    2013    8       28   2.3
    2013    8       30   1.7
    2014    8       7    3
    2014    9       11   5.8",
  header = T)

df2 <- df1 %>%
  mutate(date = ymd(paste(Year, Month, Day, sep = "-"))) %>%
  arrange(date) %>%
  mutate(is_consecutive_average = c(FALSE, diff(Average) == 0)) %>%
  mutate(is_consecutive_day = c(FALSE, diff(date) == 1)) %>%
  mutate(Average = Average * !(is_consecutive_average amp; is_consecutive_day)) %>%
  select(-is_consecutive_average, -is_consecutive_day, -date)

df2

##    Year Month Day Average
## 1  2013     8  28     2.3
## 2  2013     8  29     0.0
## 3  2013     8  30     1.7
## 4  2013     8  31     0.0
## 5  2014     8   6     3.0
## 6  2014     8   7     0.0
## 7  2014     8   8     0.0
## 8  2014     8   9     0.0
## 9  2014     9  11     5.8
## 10 2014     9  12     0.0
## 11 2014     9  13     0.0

df3 <- df2 %>%
  filter(Average != 0)

df3

##   Year Month Day Average
## 1 2013     8  28     2.3
## 2 2013     8  30     1.7
## 3 2014     8   6     3.0
## 4 2014     9  11     5.8
  

Комментарии:

1. Моя ошибка исправлена.

Ответ №2:

Вот data.table решение:

Считайте данные

 data <- readr::read_csv(
    text,
    col_names = TRUE,
    trim_ws = TRUE
)

library( data.table )
setDT( data )
  

Преобразуйте значения даты в более удобный формат и отсортируйте

 data[ , date := as.Date( paste0( Year, "-", Month, "-", Day ) ) ]
setorder( data, date )
  

Создайте новые столбцы для предыдущей даты и средних значений

 data[ , prev.date := shift( date, 1L, type = "lag" ) ]
data[ , prev.average := shift( Average, 1L, type = "lag" ) ]
  

Отметьте точки, в которых должна быть создана новая «группа», на основе ваших критериев. Также отметьте самую первую запись как начало новой группы, поскольку мы можем предположить, что это так.

 data[ , group := 0L
      ][ as.integer( date - prev.date ) > 1L |
         Average != prev.average, group := 1L 
         ][ 1L, group := 1L ]
  

Получите первый желаемый результат, заменив определенные значения нулями

 data[ group != 1L, Average := 0 ]
first.output <- data[ , .( date, Average ) ]
head( first.output, 3 )

         date Average
1: 2013-08-28     2.3
2: 2013-08-29     0.0
3: 2013-08-30     1.7
  

Теперь пометьте группы как уникальные номера

 data[ , group := cumsum( group ) ]
  

И получите свой второй результат путем агрегирования до максимального «среднего» значения (которое будет единственным, не равным нулю) и минимального значения «дата» (первое в этой группе):

 second.output <- data[ , .( date = min( date ),
                            Average = max( Average ) ), 
                       by = group ][ , .( date, Average ) ]

head( second.output, 3 )
         date Average
1: 2013-08-28     2.3
2: 2013-08-30     1.7
3: 2014-08-06     3.0
  

ПРИМЕЧАНИЕ: вы, вероятно, могли бы получить second.output , просто удалив строки с нулевым «средним» значением из first.output , но это приведет к удалению любых групп, где «среднее» действительно равно нулю, поэтому я думаю, что этот метод безопаснее.