2016-04-10 3 views
4

У этого вопроса уже есть answer for SQL, и я смог реализовать это решение в R, используя sqldf. Однако мне не удалось найти способ реализовать его, используя data.table.Количество уникальных значений в диапазоне дат прокатки для R

Задача состоит в том, чтобы подсчитать отдельные значения одного столбца в пределах диапазона дат прокатки, например. (И цитирование непосредственно из связанного вопроса), если данные выглядели так:

Date | email 
-------+---------------- 
1/1/12 | [email protected] 
1/1/12 | [email protected] 
1/1/12 | [email protected] 
1/2/12 | [email protected] 
1/2/12 | [email protected] 
1/3/12 | [email protected] 
1/4/12 | [email protected] 
1/5/12 | [email protected] 
1/5/12 | [email protected] 
1/6/12 | [email protected] 
1/6/12 | [email protected] 
1/6/12 | [email protected] 

Тогда множество результат будет выглядеть примерно так, если мы использовали период дат 3 дня

date | count(distinct email) 
-------+------ 
1/1/12 | 3 
1/2/12 | 3 
1/3/12 | 3 
1/4/12 | 3 
1/5/12 | 2 
1/6/12 | 2 

Вот код для создания тех же данных в R с помощью data.table:

date <- as.Date(c('2012-01-01','2012-01-01','2012-01-01', 
        '2012-01-02','2012-01-02','2012-01-03', 
        '2012-01-04','2012-01-05','2012-01-05', 
        '2012-01-06','2012-01-06','2012-01-06')) 
email <- c('[email protected]', '[email protected]','[email protected]', 
      '[email protected]', '[email protected]','[email protected]', 
      '[email protected]','[email protected]','[email protected]', 
      '[email protected]','[email protected]','[email protected]') 
dt <- data.table(date, email) 

Любая помощь по этому вопросу будет высоко ценится. Благодаря!

Edit 1:

Это проблема игрушки, которую я хочу, чтобы применить к гораздо большему набору данных, поэтому использование декартовых произведений является проблематичным. Вместо этого я хотел бы что-то, что эквивалентно коррелированному подзапросу в SQL, например. решение от вопроса, который я изначально связан был:

SELECT day 
    ,(SELECT count(DISTINCT email) 
     FROM tbl 
     WHERE day BETWEEN t.day - 2 AND t.day -- period of 3 days 
    ) AS dist_emails 
FROM tbl t 
WHERE day BETWEEN '2012-01-01' AND '2012-01-06' 
GROUP BY 1 
ORDER BY 1; 

Edit 2: Вот некоторые сроки на основе @ решение MichaelChirico, в соответствии с просьбой @jangorecki:

# The data 
> dim(temp) 
[1] 2627785  4 
> head(temp) 
     date category1 category2 itemId 
1: 2013-11-08   0   2 1713 
2: 2013-11-08   0   2 90485 
3: 2013-11-08   0   2 74249 
4: 2013-11-08   0   2 2592 
5: 2013-11-08   0   2 2592 
6: 2013-11-08   0   2 765 
> uniqueN(temp$itemId) 
[1] 13510 
> uniqueN(temp$date) 
[1] 127 

# Timing for data.table 
> system.time(dtTime <- temp[, 
+ .(count = temp[.(seq.Date(.BY$date - 6L, .BY$date, "day"), 
+ .BY$category1, .BY$category2), uniqueN(itemId), nomatch = 0L]), 
+ by = c("date","category1","category2")]) 
    user system elapsed 
    6.913 0.130 6.940 
> 
# Time for sqldf 
> system.time(sqlDfTime <- 
+ sqldf(c("create index ldx on temp(date, category1, category2)", 
+ "SELECT date, category1, category2, 
+ (SELECT count(DISTINCT itemId) 
+ FROM temp 
+ WHERE category1 = t.category1 AND category2 = t.category2 AND 
+ date BETWEEN t.date - 6 AND t.date 
+ ) AS numItems 
+ FROM temp t 
+ GROUP BY date, category1, category2 
+ ORDER BY 1;")) 
    user system elapsed 
87.225 0.098 87.295 

Выходы eqivalent, но использование data.table, а не sqldf привело к ускорению 12.5x. Довольно существенный!

+1

Привет, могли бы вы пожалуйста, объясните отчетный счет электронной почты, таким образом он сможет помочь. Спасибо. –

+0

Я думаю, что 1/5/12 должно иметь счет = 2. – MichaelChirico

+0

Если вы хотите только ежедневное количество уникальных писем, как насчет 'dt [, length (unique (email)), by = date]'? –

ответ

5

Вот что-то, что работает, воспользовавшись новой функцией non-equijoins data.table.

#install.packages("data.table", 
#     repos = "https://Rdatatable.github.io/data.table", 
#     type = "source") for version 1.9.7+ 

dt[dt[ , .(date3=date, date2 = date - 2, email)], 
    on = .(date >= date2, date<=date3), 
    allow.cartesian = TRUE 
    ][ , .(count = uniqueN(email)), 
     by = .(date = date + 2)] 
#   date V1 
# 1: 2011-12-30 3 
# 2: 2011-12-31 3 
# 3: 2012-01-01 3 
# 4: 2012-01-02 3 
# 5: 2012-01-03 1 
# 6: 2012-01-04 2 

Честно говоря, я немного раздражался о том, как это работает точно, но идея состоит в том, чтобы присоединиться к dt к себе на date, соответствующий любому date, который находится между 2 дня назад и сегодня. Я не уверен, почему мы должны очистить, установив затем date = date + 2.


Вот подход с использованием ключей:

setkey(dt, date) 

dt[ , .(count = dt[.(seq.Date(.BY$date - 2L, .BY$date, "day")), 
        uniqueN(email), nomatch = 0L]), by = date] 
+0

'devtools' больше не требуется для установки версии devel – jangorecki

+1

re cleanup, поле' date' от 'i' arg, это непредвиденное поведение базы R-последовательности, вы можете использовать' x.date', чтобы взять 'date' из 'x'. Описан в [data.table # 1615] (https://github.com/Rdatatable/data.table/issues/1615). – jangorecki

+0

Несмотря на то, что это делает работу для примера, использование декартового продукта проблематично. Пример, который я использовал, был полезной проблемой для игрушек, но я применяю это решение к очень большим наборам данных (> 6 миллионов строк). Можете ли вы придумать способ выполнения этой операции в data.table, избегая при этом эквивалента перекрестного соединения? – Isaac

3

С недавно внедренная non-equi присоединяется особенность в current development version из data.table, v1.9.7, это может быть сделано следующим образом:

dt[.(date3=unique(dt$date2)), .(count=uniqueN(email)), on=.(date>=date3, date2<=date3), by=.EACHI] 
#   date  date2 count 
# 1: 2011-12-30 2011-12-30  3 
# 2: 2011-12-31 2011-12-31  3 
# 3: 2012-01-01 2012-01-01  3 
# 4: 2012-01-02 2012-01-02  3 
# 5: 2012-01-03 2012-01-03  1 
# 6: 2012-01-04 2012-01-04  2