Фиктивная переменная, обусловленная повторениями в сгруппированных наблюдениях

РЕДАКТИРОВАТЬ

Спасибо за ваши ответы. Однако мне все еще не удалось решить свою проблему, поскольку мой набор данных содержит 700000 наблюдений, и все приведенные ниже подходы приводят к ошибке или просто продолжают работать часами без завершения (я могу сказать, что сеанс Rstudio R запущен и потребляет много моей оперативной памяти, но никуда не денется).

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

Любые идеи? Я пока оставлю вопрос без ответа, но если вы, ребята, подумаете, что мне следует опубликовать новый вопрос, я сделаю это (честно говоря, я не знаю этикета в отношении этих вещей, так что вы можете оставить предложение).


Исходный пост

Как следует из названия, я ищу фиктивную переменную, которая обусловлена ​​повторениями в сгруппированных наблюдениях.

Рассмотрим следующий фрейм данных:

   id name year
1   c   af 2000
2   c   el 2000
3   c   in 2000
4   c   ud 2000
5   d   ot 2000
6   d   an 2000
7   d   el 2000
8   d   un 2000
9   f   yt 2002
10  f   ip 2002
11  f   ot 2002
12  f   el 2002
13  g   yt 2003
14  g   af 2003
15  g   ol 2003
16  g   in 2003
17  h   in 2003
18  h   eg 2003
19  h   yt 2003
20  h   af 2003
21  j   ot 2004
22  j   el 2004
23  j   ip 2004
24  j   yt 2004

Я ищу функцию, которая позволит мне сгруппировать данные по идентификатору и вернуть значение «1», если идентификатор содержит не менее трех имен в предыдущем идентификаторе. Под предыдущим идентификатором я подразумеваю, что год предыдущего идентификатора должен быть меньше, чем для текущего идентификатора.

Желаемый результат должен выглядеть так:

   id name year dummy
1   c   af 2000     0
2   c   el 2000     0
3   c   in 2000     0
4   c   ud 2000     0
5   d   ot 2000     0
6   d   an 2000     0
7   d   el 2000     0
8   d   un 2000     0
9   f   yt 2002     0
10  f   ip 2002     0
11  f   ot 2002     0
12  f   el 2002     0
13  g   yt 2003     0
14  g   af 2003     0
15  g   ol 2003     0
16  g   in 2003     0
17  h   in 2003     0
18  h   eg 2003     0
19  h   yt 2003     0
20  h   af 2003     0
21  j   ot 2004     1
22  j   el 2004     1
23  j   ip 2004     1
24  j   yt 2004     1

id = "j" принимает значение dummy = "1", поскольку в id = "f" встречается как минимум три имени: "yt", "ip" и "ot". В этом случае также встречается четвертое имя, «эль», но это не влияет на результат.

Обратите внимание, что id = "h" принимает значение dummy = "0", хотя три имени также встречаются в id = "g". Это потому, что оба события произошли в 2003 году, и поэтому это не соответствует условию для отдельных лет.

Данные:

DF = structure(list(id = c("c", "c", "c", "c", "d", "d", "d", "d", 
"f", "f", "f", "f", "g", "g", "g", "g", "h", "h", "h", "h", "j", 
"j", "j", "j"), name = c("af", "el", "in", "ud", "ot", "an", 
"el", "un", "yt", "ip", "ot", "el", "yt", "af", "ol", "in", "in", 
"eg", "yt", "af", "ot", "el", "ip", "yt"), year = c(2000L, 2000L, 
2000L, 2000L, 2000L, 2000L, 2000L, 2000L, 2002L, 2002L, 2002L, 
2002L, 2003L, 2003L, 2003L, 2003L, 2003L, 2003L, 2003L, 2003L, 
2004L, 2004L, 2004L, 2004L), dummy = c(0L, 0L, 0L, 0L, 0L, 0L, 
0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 1L, 1L, 
1L, 1L)), .Names = c("id", "name", "year", "dummy"), row.names = c(NA, 
-24L), class = "data.frame")

person Lucas E    schedule 03.10.2018    source источник
comment
для id = h и year = 2003 имена in и af встречаются в id = c и year = 2000, а имя yt встречается в id = f и year = 2002, тогда не должно ли dummy быть 1?   -  person chinsoon12    schedule 04.10.2018
comment
Нет, имена должны встречаться с одним и тем же идентификатором, в противном случае условие идентификатора, имеющего как минимум три одинаковых имени, не будет выполнено.   -  person Lucas E    schedule 06.10.2018


Ответы (6)


После редактирования OP о проблемах скорости и памяти, как насчет подхода Rcpp:

#create a integer column out of id for non-equi join later
setDT(DF)[, nid := rleid(id)]

#convert name into an integer code
DF[DF[,.(name=unique(name))][, IntCode := .I], iname := IntCode, on=.(name)]

library(inline)
library(Rcpp)
cppFunction('
NumericVector hasOccur(NumericVector nid, NumericVector year, List iname) {
    List namelist(iname);
    int sz = namelist.size(), i, j, m, n, nPrev, nCurr, count;
    NumericVector res(sz);

    for(i=0; i<sz; i++) {
        for(j=0; j<i; j++) {
            if (nid[j] < nid[i] && year[j] < year[i]) {
                SEXP prevList = namelist[j];
                SEXP currList = namelist[i];

                NumericVector cl(currList);
                NumericVector pl(prevList);
                nPrev = pl.size();
                nCurr = cl.size();

                res[i] = 0;
                count = 0;
                for(m=0; m<nCurr; m++) {
                    for (n=0; n<nPrev; n++) {
                        if (cl[m] == pl[n]) {
                            count++;
                            break;
                        }
                    }
                }

                if (count >= 3) {
                    res[i] = 1;
                    break;
                }
            }
        }
    }

    return(res);
}')

d <- DF[, .(.(nm=iname)), by=.(nid, year)]
DF[d[, dummy := hasOccur(d$nid, d$year, d$V1)], dummy := dummy, on=.(nid, year)]

HTH.


Другой возможный data.table подход:

#create a integer column out of id for non-equi join later
setDT(DF)[, nid := rleid(id)]

          #self non-equi join
check3 <- DF[DF, .(x.id, x.name, x.year, x.nid, i.id, i.name, i.year, i.nid), on=.(nid<nid, year<year, name=name)][,
    #count the number of occurrence in previous id and year
    uniqueN(x.name, na.rm=TRUE), by=.(i.id, i.year, x.id, x.year)][,
        #check if more than 3
        any(V1 >= 3L), by=.(i.id, i.year)]

#update join to add result to original DF
DF[check3, dummy := as.integer(V1), on=c("id"="i.id", "year"="i.year")]
person chinsoon12    schedule 09.10.2018
comment
Спасибо за ваше решение. Это работает для примера, но не применительно к моему набору данных наблюдений ~ 700 000 (см. Мою правку в вопросе). - person Lucas E; 09.10.2018
comment
@LucasE Я добавил Rcpp подход - person chinsoon12; 10.10.2018
comment
Я установил необходимые пакеты и попытался запустить код. Через час Rstudio все еще выполнял код, и мне пришлось его завершить. На этом этапе Rstudios перестал отвечать, и мне пришлось перезапустить Rstudios. Но все равно спасибо! (Хочу отметить, что я работаю со своего рабочего ноутбука, который может оказаться недостаточно сложным для выполнения таких больших операций.) - person Lucas E; 10.10.2018
comment
@LucasE Могу я спросить, работает ли решение сейчас? Или вы просто пытаетесь закрыть вопрос? - person chinsoon12; 24.10.2018

Подход в базе R:

n <- split(DF$name, DF$id)
m1 <- sapply(n, function(s1) sapply(n, function(s2) sum(s1 %in% s2) ))
diag(m1) <- 0
m1[upper.tri(m1)] <- 0
r1 <- rownames(m1)[!!rowSums(m1 > 2)]

y <- sapply(split(DF$year, DF$id), unique)
m2 <- sapply(y, function(s1) sapply(y, function(s2) +(s1 == s2) ))
diag(m2) <- 0
m2[upper.tri(m2)] <- 0
r2 <- rownames(m2)[!rowSums(m2)]

DF$dummy2 <- as.integer(DF$id %in% intersect(r1,r2))

который дает:

> DF
   id name year dummy dummy2
1   c   af 2000     0      0
2   c   el 2000     0      0
3   c   in 2000     0      0
4   c   ud 2000     0      0
5   d   ot 2000     0      0
6   d   an 2000     0      0
7   d   el 2000     0      0
8   d   un 2000     0      0
9   f   yt 2002     0      0
10  f   ip 2002     0      0
11  f   ot 2002     0      0
12  f   el 2002     0      0
13  g   yt 2003     0      0
14  g   af 2003     0      0
15  g   ol 2003     0      0
16  g   in 2003     0      0
17  h   in 2003     0      0
18  h   eg 2003     0      0
19  h   yt 2003     0      0
20  h   af 2003     0      0
21  j   ot 2004     1      1
22  j   el 2004     1      1
23  j   ip 2004     1      1
24  j   yt 2004     1      1
person Jaap    schedule 03.10.2018

Подобно Jaap и see24, но с использованием length(intersect(x,y)) вместо _2 _ / _ 3_ с _4 _ / _ 5_:

library(data.table)
setDT(DF)
idDT = unique(DF[, .(id, year)])
setkey(idDT, id)

s = split(DF$name, DF$id)

# identify pairs of ids, where id1 appears before id2 in the table
pairsDT = idDT[, CJ(id1 = id, id2 = id)[id1 < id2]]

# record whether it's strictly before 
pairsDT[, earlier := idDT[id1, x.year] < idDT[id2, x.year]]

# if it's strictly before, compare number of matching elements
pairsDT[earlier == TRUE, matched := 
  mapply(function(x, y) length(intersect(x, y)), s[id1], s[id2]) >= 3
]

dum_ids = pairsDT[matched == TRUE, unique(id2)]

Затем вы можете записать критерий в idDT (там, где было бы больше смысла) или DF:

idDT[, dum := id %in% dum_ids]
DF[, dum := id %in% dum_ids]

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

person Frank    schedule 03.10.2018
comment
Фрэнк, спасибо, что подняли на экран igraph. Это развлечение для меня - person struggles; 04.10.2018

Вот мое решение с использованием dplyr и tidyr и функции для идентификации идентификаторов с 3 или более совпадающими именами:

library(dplyr)
library(tidyr)

test <- function(x){
  out2 <- sapply(1:length(x), function(j){
    out <- sapply(1:j, function(i){
      sum(x[[j]] %in% x[[i]])
    })
    out[j]<-NA
    which(out >= 3) %>% min() %>% {ifelse(is.infinite(.),NA,.)}

  })
  out2
}

DF2 <-  DF %>% group_by(id, year) %>% 
  summarise(names = list(name)) %>% ungroup() %>% 
  mutate(dummy2 = test(names)) %>% 
  mutate(year_mch = year[dummy2], 
         dummy = year_mch < year) %>% 
  unnest() 
DF2

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

person see24    schedule 03.10.2018

Итак, это решение является чистой базой R. Однажды я прочитал статью, в которой утверждалось, что использование . <- является допустимой заменой %>%. Я пробовал это впервые. Я думаю мне это нравится

. <- DF[c('id', 'name', 'year')]
. <- merge(., ., by = 'name')
. <- .[.["id.x"] != .["id.y"] & .["year.x"] < .["year.y"],]
. <- .[c('id.x', 'id.y', 'year.x', 'year.y', "name")] 
.$n <- 1
. <- aggregate(n ~ id.x + id.y, data = ., sum) 
. <- .[.['n'] >= 3, 'id.y']

DF$dummy2 <- . == DF$id
person Beemyfriend    schedule 04.10.2018

Я воспользуюсь любым предлогом, чтобы преобразовать проблему с данными в проблему с графом, так что искренне приветствую за то, что поднял этот вопрос. Вот решение igraph. По сути, он преобразует данные в ориентированное дерево. Все узлы сравниваются только с узлами выше в иерархии. Таким образом, C является вершиной дерева и не сравнивается ни с чем другим, в то время как J является терминалом и сравнивается со всеми узлами над ним в цепочке. Чтобы вытащить все узлы, находящиеся выше в иерархии, все, что вам нужно сделать, это использовать функцию (поиск в глубину) dfs

library(tidyverse)
library(igraph)

#node list containing data specific to the group
nodelist <- DF %>%
  group_by(id, year) %>%
  nest()

#edge list containing connections. A group directly before a node points toward a future group
edgelist <- data.frame(
  from = nodelist$id %>% .[1:(length(.)-1)],
  to = nodelist$id %>% .[2:length(.)]
)

#create the data frame
g <- graph_from_data_frame(edgelist, T, nodelist)

#let's iterate through the nodes
dummy <- map_lgl(V(g)$name, function(vertex){

  #depth first search to pull out all nodes higher up on the tree 
  full_path <- dfs(g, vertex, 'in', unreachable = F) %>%
    .$order %>% 
    .[!is.na(.)] 

  #if there is no node higher up, then we're done
  if(length(full_path) <= 1) return(F)

  #The first node returned is the node we're iterating over
  this_vertex <- full_path[1]
  other_vertices <- full_path[full_path != this_vertex]

  #this is the logic for the dummy variable
  similar_groups <- map_lgl(other_vertices, function(other_vertex){
    (sum(this_vertex$data[[1]]$name %in% other_vertex$data$name) >= 3) & 
      (this_vertex$year[[1]] != other_vertex$year)
  })

  return(T %in% similar_groups)
})

V(g)$dummy2 <- dummy

as_data_frame(g, 'vertices') %>%
  unnest()

введите здесь описание изображения

   name year dummy2 name1 dummy
1     c 2000  FALSE    af     0
2     c 2000  FALSE    el     0
3     c 2000  FALSE    in     0
4     c 2000  FALSE    ud     0
5     d 2000  FALSE    ot     0
6     d 2000  FALSE    an     0
7     d 2000  FALSE    el     0
8     d 2000  FALSE    un     0
9     f 2002  FALSE    yt     0
10    f 2002  FALSE    ip     0
11    f 2002  FALSE    ot     0
12    f 2002  FALSE    el     0
13    g 2003  FALSE    yt     0
14    g 2003  FALSE    af     0
15    g 2003  FALSE    ol     0
16    g 2003  FALSE    in     0
17    h 2003  FALSE    in     0
18    h 2003  FALSE    eg     0
19    h 2003  FALSE    yt     0
20    h 2003  FALSE    af     0
21    j 2004   TRUE    ot     1
22    j 2004   TRUE    el     1
23    j 2004   TRUE    ip     1
24    j 2004   TRUE    yt     1
person struggles    schedule 03.10.2018
comment
Интересный подход! Однако он возвращает сообщение об ошибке: > g <- graph_from_data_frame(edgelist, T, nodelist) Error in graph_from_data_frame(edgelist, T, nodelist) : Duplicate vertex names. y И, как следствие, приводит к следующим ошибкам: + return(T %in% similar_groups) + }) Error in "igraph" %in% class(graph) : object 'g' not found > > V(g)$dummy2 <- dummy Error in V(g)$dummy2 <- dummy : object 'g' not found > > as_data_frame(g, 'vertices') %>% + unnest() Error in "igraph" %in% class(graph) : object 'g' not found. Есть предположения? - person Lucas E; 09.10.2018
comment
ваш список узлов должен иметь уникальные значения в первом столбце. - person struggles; 09.10.2018
comment
Верно! После фильтрации дубликатов мне удалось запустить код, так что большое спасибо! Я также добавил индикатор выполнения для отслеживания процесса. Кажется, что цикл через «фиктивную» функцию занимает около 12 секунд, и, похоже, он замедляется. Если это будет сделано для всех 71883 наблюдений, Rstudio потребуется как минимум 10 дней, чтобы выполнить функцию. Я абсолютно считаю этот подход красивым и интуитивно понятным, но как вы думаете, он самый быстрый? - person Lucas E; 09.10.2018