Цикл / если еще в R для фрейма данных

#r #loops #if-statement #dataframe

#r #циклы #if-оператор #фрейм данных

Вопрос:

Я действительно застрял на выполнении цикла в R. Я тоже пытался использовать ifelse, но, похоже, просто не могу получить результат.

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

 ID     |  Date     |   Mode  |  Time
------ | --------- | ------- | -----
1234   | 12/10/16  | Bus     |  120 
1234   | 12/10/16  | Bus     |  130
1234   | 12/10/16  | Bus     |  290
1234   | 12/10/16  | Train   |  310
1234   | 12/10/16  | Bus     |  330
4567   | 12/10/16  | Bus     |  220 
4567   | 12/10/16  | Bus     |  230
4567   | 13/10/16  | Bus     |  290
4567   | 13/10/16  | Bus     |  450
4567   | 14/10/16  | Train   |  1000
  

Итак, 12/10 клиент 1234 создал 4 шины jny и 1 поезд jny.

Я хочу создать 5-й столбец, в котором указывается, связаны ли этапы путешествия, т.Е. Связано ли 2-е путешествие с 1-м путешествием, связано ли 3-е путешествие со 2-м путешествием (где 1 = связано, 0 = не связано).).

Должны выполняться следующие условия:

  • jnys предназначены для одного и того же человека и выполняются в один и тот же день

  • 2 поездки на автобусе находятся в пределах 60 минут друг от друга (поэтому поездки на автобусе и поезде в пределах 60 минут друг от друга не будут связаны)

  • если i 1-й и i-й переход связаны, то i 1-й переход не может быть связан с i 2-м путешествием

Я бы хотел, чтобы результат был следующим:

 ID     |  Date     |   Mode  |  Time  | Linked
------ | --------- | ------- | -----  | -----
1234   | 12/10/16  | Bus     |  120   |  0
1234   | 12/10/16  | Bus     |  130   |  1
1234   | 12/10/16  | Bus     |  290   |  0
1234   | 12/10/16  | Train   |  310   |  0
1234   | 12/10/16  | Bus     |  330   |  0
4567   | 12/10/16  | Bus     |  220   |  0
4567   | 12/10/16  | Bus     |  230   |  1
4567   | 13/10/16  | Bus     |  290   |  0
4567   | 13/10/16  | Bus     |  450   |  0
4567   | 14/10/16  | Train   |  1000  |  0
  

Любая помощь будет высоко оценена!

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

1. Было бы очень полезно показать какие-либо усилия самостоятельно!

2. У меня буквально ничего с этим не получилось

Ответ №1:

1) ave Попробуйте это:

 transform(DF, linked = ave(Time, ID, Date, cumsum(c(FALSE, Mode[-1] != Mode[-nrow(DF)])), 
      FUN = function(x) c(0, diff(x) < 60)))
  

предоставление:

      ID     Date  Mode Time linked
1  1234 12/10/16   Bus  120      0
2  1234 12/10/16   Bus  130      1
3  1234 12/10/16   Bus  290      0
4  1234 12/10/16 Train  310      0
5  1234 12/10/16   Bus  330      0
6  4567 12/10/16   Bus  220      0
7  4567 12/10/16   Bus  230      1
8  4567 13/10/16   Bus  290      0
9  4567 13/10/16   Bus  450      0
10 4567 14/10/16 Train 1000      0
  

2) sqldf Вот решение с использованием sqldf.

 library(sqldf)
sqldf("select a.*, coalesce(a.ID = b.ID and 
                            a.Date = b.Date and 
                            a.Mode = b.Mode and 
                            a.Time < b.Time   60, 0) linked 
       from DF a left join DF b on a.rowid = b.rowid   1")
  

3) data.table Обратите внимание, что data.table имеет тенденцию быть как быстрым, так и эффективным с точки зрения памяти и может обрабатывать размеры данных в памяти, которые другие подходы не могут.

 library(data.table)

dt <- as.data.table(DF)
dt[, linked := (Time < shift(Time, fill = -60)   60) * 
               (Mode == shift(Mode, fill = Mode[1])), by = "ID,Date"]
  

4) dplyr

 library(dplyr)
DF %>% 
   group_by(ID, Date) %>%
   mutate(linked = (Time < lag(Time, default = -Inf)   60) * 
                   (Mode == lag(Mode, default = Mode[1]))) %>%
   ungroup()
  

давая аналогичный ответ.

Примечание: входные DF данные в воспроизводимой форме:

 Lines <- 
"ID     |  Date     |   Mode  |  Time
------ | --------- | ------- | -----
1234   | 12/10/16  | Bus     |  120 
1234   | 12/10/16  | Bus     |  130
1234   | 12/10/16  | Bus     |  290
1234   | 12/10/16  | Train   |  310
1234   | 12/10/16  | Bus     |  330
4567   | 12/10/16  | Bus     |  220 
4567   | 12/10/16  | Bus     |  230
4567   | 13/10/16  | Bus     |  290
4567   | 13/10/16  | Bus     |  450
4567   | 14/10/16  | Train   |  1000"
DF <- read.table(text = Lines, header = TRUE, sep = "|", strip.white = TRUE,
 comment = "-", as.is = TRUE)
  

Обновление: исправлено.

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

1. Оцените ответ — есть ли способ избежать чтения таблицы? Таблица довольно большая, поэтому это не кажется правильным

2. R работает только с фреймами данных в памяти, поэтому вам нужно загрузить данные. Если ваши данные находятся в базе данных, вы можете попробовать использовать, например dplyr , пакет. Он преобразует операторы R в SQL, выполняемые внутри подключения к базе данных, а затем возвращает только то подмножество, которое вас интересует.

3. Добавлено решение sqldf. В его нынешнем виде предполагается, что DF находится в памяти, но вы, вероятно, могли бы адаптировать SQL к настройке вашей базы данных, какой бы она ни была.

Ответ №2:

Мне нравится ответ Гротендика, но это может быть не так просто интерпретировать для кого-то нового для R. Итак, давайте сделаем это менее программно эффективным способом, который покажет вам шаги, которые нужно предпринять. Я буду использовать то же соглашение об именовании фреймов данных, что и Гротендик.

Давайте определим, находится ли время между переходами в пределах 60 минут. Позволяет перебирать все строки в фрейме данных, и если это одна и та же учетная запись, и если они имеют одинаковый тип режима, то проверьте, находятся ли они на расстоянии менее 60 минут друг от друга, и если все три условия выполняются, тогда установите значение linked равным 1. В противном случае мы установим linked равным 0.

 for (i in 2:dim(df)[1]){
  if (df$ID[i]==df$ID[i-1]){
    if (df$Mode[i]==df$Mode[i-1]){
      if ((df$Time[i]-df$Time[i-1]) < 60){
        df$linked[i] <- 1
      }
      else {
        df$linked[i] <- 0
      }
    }
    else {
      df$linked[i] <- 0
    }
  }
  else {
    df$linked[i] <- 0
  }
}
  

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

1. разве вы не хотите df$linked[i] , а не df$linked ? Также: 1 для логики, но обратите внимание, что это, вероятно, будет во много раз медленнее, чем @G. Ответ Гротендика …

2. Конечно G. Grothendieck , можно сделать менее критичным, но R на самом деле побуждает вас использовать векторизованные операции, такие как diff() и cumsum() . Векторизованные операции также могут быть легко интерпретированы для кого-то, кто не знаком с R.

3. Это то, что я пробовал, однако я не могу заставить его работать, потому что в нем говорится об ошибке:

4. Ошибка: неожиданное ‘<‘ в: » if (df $MODE[i]==df $MODE[i-1]) { if (df $TIME[i]-df $TIME[i-1])<»

5. Я немного изменил это, но каждая запись возвращает 1 в связанном столбце. Я не уверен, почему, поскольку я думал, что это кажется логичным подходом. Я адаптировался к использованию операторов ‘and’, но все равно получил то же самое

Ответ №3:

Использование dplyr пакета:

 library(dplyr)
DF %>%
    # The journeys are for the same person, take place on the same day
    # and on the same mode of transport
    group_by(ID, Date, Mode) %>% 
    # 2 bus journeys are within 60 mins of one another 
    mutate(linked0 = c(Inf, diff(Time))<60, 
           # if the i 1th and the ith journey are linked, 
           # then the i 1th journey cannot be linked to the i 2th journey
           linkedsum = cumsum(linked0),
           linked = ifelse(linkedsum==1, linked0, 0))

      ID     Date  Mode  Time linked0 linkedsum linked
   <int>    <chr> <chr> <int>   <lgl>     <int>  <dbl>
1   1234 12/10/16   Bus   120   FALSE         0      0
2   1234 12/10/16   Bus   130    TRUE         1      1
3   1234 12/10/16   Bus   290   FALSE         1      0
4   1234 12/10/16 Train   310   FALSE         0      0
5   1234 12/10/16   Bus   330    TRUE         2      0
6   4567 12/10/16   Bus   220   FALSE         0      0
7   4567 12/10/16   Bus   230    TRUE         1      1
8   4567 13/10/16   Bus   290   FALSE         0      0
9   4567 13/10/16   Bus   450   FALSE         0      0
10  4567 14/10/16 Train  1000   FALSE         0      0
  

Чтобы выполнить это внутри базы данных, см. Виньетку базы данных dplyr.