Reputation: 1172
I am working on a large data.table (2.5 million rows) of interbank loans. Here is an extract of the first 20:
> dput(head(clean,20))
structure(list(time = c(4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 3L, 4L,
4L, 4L, 1L, 2L, 3L, 4L, 3L, 4L, 4L, 4L), bal = structure(c(2L,
4L, 4L, 4L, 4L, 4L, 3L, 3L, 9L, 4L, 2L, 3L, 3L, 3L, 3L, 2L, 4L,
5L, 2L, 15L), .Label = c("32001", "32002", "32003", "32004",
"32005", "32006", "32007", "32008", "32009", "32010", "32201",
"32202", "32203", "32204", "32205", "32206", "32207", "32208",
"32209", "32210"), class = "factor"), lender = c(2003L, 2547L,
2547L, 574L, 574L, 574L, 2984L, 3015L, 812L, 3278L, 3124L, 3124L,
41L, 354L, 3156L, 3156L, 735L, 735L, 1421L, 3319L), borrower = c(2285L,
2285L, 2285L, 2285L, 2285L, 2285L, 2285L, 2285L, 269L, 2839L,
2839L, 2839L, 2839L, 2897L, 2399L, 2399L, 1816L, 1816L, 2476L,
3033L), obm = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0), obd = c(1, 0.3, 0.2, 0.35, 0.7, 0.5, 0.4, 1.2,
4, 0.16, 4, 4, 0.5, 0.1, 1.4, 1.4, 4, 1, 3.25, 0.4), obk = c(1,
0, 0, 0, 0, 0, 0, 0.5, 0, 0, 0, 4, 0.5, 0.1, 0, 0, 0, 0, 3.25,
0), oem = c(0, 0.3, 0.2, 0.35, 0.7, 0.5, 0.4, 0.7, 4, 0.16, 4,
0, 0, 0, 1.4, 1.4, 4, 1, 0, 0.4), r = c(35, 63, 63, 63, 63, 63,
60, 60, 3, 55, 25, 12, 34, 0, 5, 4, 60, 60, 60, 35), type = structure(c(1L,
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L,
1L, 1L, 2L), .Label = c("loan", "deposit"), class = "factor"),
term = structure(c(2L, 4L, 4L, 4L, 4L, 4L, 3L, 3L, 9L, 4L,
2L, 3L, 3L, 3L, 3L, 2L, 4L, 5L, 2L, 5L), .Label = c("overdraft",
"<1d", "2-7d", "8-30d", "31-90d", "91-180d", "0.5-1y", "1-3y",
">3y", "demand"), class = "factor"), reported = structure(c(10561,
10561, 10561, 10561, 10561, 10561, 10561, 10561, 10531, 10561,
10561, 10561, 10470, 10500, 10531, 10561, 10531, 10561, 10561,
10561), class = "Date"), issued = structure(c(10542, 10543.5,
10550, 10556.5, 10553.5, 10555.5, 10558, 10558, 10515, 10557.5,
10560, 10555, 10465, 10488, 10527, 10560, 10515.5, 10545.5,
10541, 10544), class = "Date"), issued_radius = c(0, 10.5,
10, 3.5, 6.5, 4.5, 2, 2, 15, 2.5, 0, 2, 2, 2, 2, 0, 10.5,
14.5, 0, 13), due = structure(c(10543, 10563, 10570, 10583,
10577, 10581, 10563, 10563, 11966, 10585, 10561, 10560, 10470,
10493, 10532, 10561, 10535, 10611, 10542, 10589), class = "Date"),
month = c(4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 1, 2, 3, 4,
3, 4, 4, 4), week = c(14, 14, 15, 16, 16, 16, 17, 17, 10,
16, 17, 16, 3, 7, 12, 17, 10, 15, 14, 15)), .Names = c("time",
"bal", "lender", "borrower", "obm", "obd", "obk", "oem", "r",
"type", "term", "reported", "issued", "issued_radius", "due",
"month", "week"), class = c("data.table", "data.frame"), row.names = c(NA,
-20L), .internal.selfref = <pointer: 0x2960818>)
The three columns of interest in clean
are issued
, issued_radius
and week
, but I have included all columns since they affect the loop's performance.
Every row stands for a loan that I want to estimate the Date
of issuance for, with weekly resolution. This issuance date lies in the interval [issued - issued_radius
, issued + issued_radius
]. This interval could span 1 day, or maybe several weeks (with a maximum of one month, or a maximum of 5 weeks). The code generates this interval and counts how many weeks, from a offset date, are included in the interval. Each of these weeks is assigned a weight consistent with the overlap. For example, one loan in clean
that could be issued in week 17 and week 18, as derived from the interval, gets expanded into two loans in patch
and the loan volume (columns oem
, obd
, etc.) are scaled with this weight.
library(data.table)
START_DATE = as.Date("1998-8-1")
elapsed_weeks <- function(t, start_date) {
as.numeric( floor( difftime( t, start_date, units="weeks" ) ) )
}
#load("clean.Rda")
# One-day intervals can be added to our result immediately
patch = clean[issued_radius==0]
clean = clean[issued_radius!=0]
N = nrow(clean)
write_index = nrow(patch)+1
# Allocate space in patch.
dummy = data.table(time = rep(0, N*5))
patch = rbindlist(list(patch, dummy), use.names = TRUE, fill= TRUE)
for (k in 1:N) {
entry = clean[k]
# Recover Date interval [i, j].
i = entry$issued - entry$issued_radius
j = entry$issued + entry$issued_radius
# Generate sequence of days in the interval and
# map each day to a weeknumber, counting the frequencies.
x = seq.Date(i, j, by="day")
T = table(elapsed_weeks(x, START_DATE))
for (name in names(T)) { # can this be vectorized?
week_number = as.numeric(name)
week_weight = as.numeric(T[name]) / length(x)
new_entry = entry
new_entry$week = week_number
new_entry$obm = entry$obm * week_weight
new_entry$obd = entry$obd * week_weight
new_entry$obk = entry$obk * week_weight
new_entry$oem = entry$oem * week_weight
patch[write_index] = new_entry
write_index = write_index + 1
}
}
# Delete unused allocated rows.
patch = patch[!is.na(type)]
print(nrow(patch)/nrow(clean)) # < 5
edit 2: Adding another example.
> clean[2]
time bal lender borrower obm obd obk oem r type term reported issued issued_radius due
1: 4 32004 2547 2285 0 0.3 0 0.3 63 loan 8-30d 1998-12-01 1998-11-13 10.5 1998-12-03
month week
1: 4 14
For this loan, it could be issued on any day in [1998-11-3
,1998-11-24
]. Every day in this interval is mapped to a the number of weeks it is offset from START_DATE:
> x
[1] "1998-11-03" "1998-11-04" "1998-11-05" "1998-11-06" "1998-11-07" "1998-11-08" "1998-11-09" "1998-11-10"
[9] "1998-11-11" "1998-11-12" "1998-11-13" "1998-11-14" "1998-11-15" "1998-11-16" "1998-11-17" "1998-11-18"
[17] "1998-11-19" "1998-11-20" "1998-11-21" "1998-11-22" "1998-11-23" "1998-11-24"
> elapsed_weeks(x, START_DATE)
[1] 13 13 13 13 14 14 14 14 14 14 14 15 15 15 15 15 15 15 16 16 16 16
Now we make a frequency table to deduce the weight for each possible week of issuance for the loan.
> table(elapsed_weeks(x, START_DATE))
13 14 15 16
4 7 7 4
So this loan will be expanded into for loans with week
columns {13, 14, 15, 16}. The volume's of this loans are scaled with the frequency weights of the set of possible weekly offsets.
> table(elapsed_weeks(x, START_DATE)) / length(x)
13 14 15 16
0.1818182 0.3181818 0.3181818 0.1818182
Thus we end up with patch
looking like this:
> patch
time bal lender borrower obm obd obk oem r type term reported issued
1: 4 32004 2547 2285 0 0.05454545 0 0.05454545 63 loan 8-30d 1998-12-01 1998-11-13
2: 4 32004 2547 2285 0 0.09545455 0 0.09545455 63 loan 8-30d 1998-12-01 1998-11-13
3: 4 32004 2547 2285 0 0.09545455 0 0.09545455 63 loan 8-30d 1998-12-01 1998-11-13
4: 4 32004 2547 2285 0 0.05454545 0 0.05454545 63 loan 8-30d 1998-12-01 1998-11-13
issued_radius due month week
1: 10.5 1998-12-03 4 13
2: 10.5 1998-12-03 4 14
3: 10.5 1998-12-03 4 15
4: 10.5 1998-12-03 4 16
I already did some optimization thanks to @David (How to speed up rbind?), but the result is still very slow. After ten hours of nightly computation I had processed 4% of the clean
data table.
So my question is: how can I scale this loop to a large data.table?
Thank you all for your time.
edit: R version 3.3.1 (2016-06-21).
Upvotes: 2
Views: 169
Reputation: 132706
If I've understood your explanation correctly, you should use an overlaps join in data.table.
#define start and end dates,
#fractional days could be an issue here, but I have not checked that further
DT[, c("start", "end") := .(issued - issued_radius, issued + issued_radius)]
#create an ID
DT[, id := .I]
#create a data.table with start of week and end of week for whole year
weeks <- data.table(date = seq(as.Date("1998-01-01"), as.Date("1998-12-31"), by = "1 day"))
weeks[, week := week(date)]
weeks <- weeks[, .(start = min(date), end = max(date)), by = week]
setkey(weeks, start, end)
#now an overlaps join
DT1 <- foverlaps(DT, weeks)
#calculate number of days in each week,
#special handling of last and first week of year might be necessary here
DT1[, overlap := 7 - (i.start > start) * (i.start - start) - (i.end < end) * (end - i.end)]
#calculate weights
DT1[, weight := as.numeric(overlap) / sum(as.numeric(overlap)), by = id]
#apply weights
DT1[, c("obm_w", "obd_w", "obk_w", "oem_w") := lapply(.SD, function(x) x * DT1[["weight"]]),
.SDcols = c("obm", "obd", "obk", "oem")]
Please check carefully if this does what you need and adjust as necessary.
Upvotes: 5