Reputation: 680
Using R, I have a large data frame, of which the following is an example:
df = data.frame(X1 = c("02JAN2008","09:30 - 10:00", "10:00 - 10:30", "10:30 - 11:00","11:00 - 11:30", "15:30 - 16:00", "16:00 - 16:30", "03JAN2008", "09:30 - 10:00", "10:00 - 10:30", "10:30 - 11:00", "11:00 - 11:30"),X2 = c(NA, 1469.37, 1459.91, 1456.92, 1453.48, 1447.22, 1447.16,NA, 1449.78, 1451.21, 1450.08, 1452.16),X3 = c(NA, 1467.97, 1467.11, 1459.76, 1457.00, 1444.00, 1447.67,NA, 1447.55, 1450.66, 1452.06, 1450.01))
which looks like:
X1 X2 X3
1 02JAN2008 NA NA
2 09:30 - 10:00 1469.37 1467.97
3 10:00 - 10:30 1459.91 1467.11
4 10:30 - 11:00 1456.92 1459.76
5 11:00 - 11:30 1453.48 1457.00
6 15:30 - 16:00 1447.22 1444.00
7 16:00 - 16:30 1447.16 1447.67
8 03JAN2008 NA NA
9 09:30 - 10:00 1449.78 1447.55
10 10:00 - 10:30 1451.21 1450.66
11 10:30 - 11:00 1450.08 1452.06
12 11:00 - 11:30 1452.16 1450.01
Due to missing data, for some days there might be 6 observations, but for others there might only be 4 (or less, this is an example).
I would like to transform this into a data frame with the date as a separate column for each 30 minute interval, such as:
X1 X2 X3 X4
1 02JAN2008 09:30 - 10:00 1469.37 1467.97
2 02JAN2008 10:00 - 10:30 1459.91 1467.11
3 02JAN2008 10:30 - 11:00 1456.92 1459.76
4 02JAN2008 11:00 - 11:30 1453.48 1457.00
5 02JAN2008 15:30 - 16:00 1447.22 1444.00
6 02JAN2008 16:00 - 16:30 1447.16 1447.67
7 03JAN2008 09:30 - 10:00 1449.78 1447.55
8 03JAN2008 10:00 - 10:30 1451.21 1450.66
9 03JAN2008 10:30 - 11:00 1450.08 1452.06
10 03JAN2008 11:00 - 11:30 1452.16 1450.01
I could easily grab the indexes of df
where X2 is NA
and then write a for
loop that carries forward the date, but I would like to avoid a for
loop in R.
How can I do this in R? Surely a dplyr
or tidyr
solution is available, but I can't produce one from the examples on the documentation. Or perhaps some version of melt
?
Upvotes: 3
Views: 1712
Reputation: 765
I have tried this:
> na_ind <- which(is.na(df$X2))
> day_break <- c(na_ind, nrow(df) + 1)
> day_count <- day_break[-1] - day_break[-length(day_break)] -1
> day_count
## [1] 6 4
> new_df <- cbind(date = rep(df$X1[na_ind], times = day_count),
+ df[-na_ind,])
> new_df
## date X1 X2 X3
## 2 02JAN2008 09:30 - 10:00 1469.37 1467.97
## 3 02JAN2008 10:00 - 10:30 1459.91 1467.11
## 4 02JAN2008 10:30 - 11:00 1456.92 1459.76
## 5 02JAN2008 11:00 - 11:30 1453.48 1457.00
## 6 02JAN2008 15:30 - 16:00 1447.22 1444.00
## 7 02JAN2008 16:00 - 16:30 1447.16 1447.67
## 9 03JAN2008 09:30 - 10:00 1449.78 1447.55
## 10 03JAN2008 10:00 - 10:30 1451.21 1450.66
## 11 03JAN2008 10:30 - 11:00 1450.08 1452.06
## 12 03JAN2008 11:00 - 11:30 1452.16 1450.01
Upvotes: 0
Reputation: 44614
Here's a dplyr way:
breaks <- is.na(df$X2)
df %>%
mutate(date=X1[breaks][cumsum(breaks)]) %>%
filter(!breaks)
# X1 X2 X3 date
# 1 09:30 - 10:00 1469 1468 02JAN2008
# 2 10:00 - 10:30 1460 1467 02JAN2008
# 3 10:30 - 11:00 1457 1460 02JAN2008
# 4 11:00 - 11:30 1453 1457 02JAN2008
# 5 15:30 - 16:00 1447 1444 02JAN2008
# 6 16:00 - 16:30 1447 1448 02JAN2008
# 7 09:30 - 10:00 1450 1448 03JAN2008
# 8 10:00 - 10:30 1451 1451 03JAN2008
# 9 10:30 - 11:00 1450 1452 03JAN2008
# 10 11:00 - 11:30 1452 1450 03JAN2008
Or just as simply in base R:
df <- within(df, date <- X1[breaks][cumsum(breaks)])
df[! breaks, ]
Upvotes: 4
Reputation: 49448
Here's an option:
library(data.table)
dt = as.data.table(df) # or setDT to convert in place
dt[, grp := cumsum(is.na(X2))][, c(date = list(X1[1]), tail(.SD, -1)), by = grp]
# grp date X1 X2 X3
# 1: 1 02JAN2008 09:30 - 10:00 1469.37 1467.97
# 2: 1 02JAN2008 10:00 - 10:30 1459.91 1467.11
# 3: 1 02JAN2008 10:30 - 11:00 1456.92 1459.76
# 4: 1 02JAN2008 11:00 - 11:30 1453.48 1457.00
# 5: 1 02JAN2008 15:30 - 16:00 1447.22 1444.00
# 6: 1 02JAN2008 16:00 - 16:30 1447.16 1447.67
# 7: 2 03JAN2008 09:30 - 10:00 1449.78 1447.55
# 8: 2 03JAN2008 10:00 - 10:30 1451.21 1450.66
# 9: 2 03JAN2008 10:30 - 11:00 1450.08 1452.06
#10: 2 03JAN2008 11:00 - 11:30 1452.16 1450.01
Upvotes: 4
Reputation: 886938
A base R
option would be
df$X1 <- as.character(df$X1)
indx <- !grepl(':', df$X1)
res <- setNames(data.frame(unlist(tapply(df$X1[indx][cumsum(indx)],
cumsum(indx), FUN=head, -1)), df[!indx,]), paste0("X",1:4))
row.names(res) <- NULL
res
# X1 X2 X3 X4
#1 02JAN2008 09:30 - 10:00 1469.37 1467.97
#2 02JAN2008 10:00 - 10:30 1459.91 1467.11
#3 02JAN2008 10:30 - 11:00 1456.92 1459.76
#4 02JAN2008 11:00 - 11:30 1453.48 1457.00
#5 02JAN2008 15:30 - 16:00 1447.22 1444.00
#6 02JAN2008 16:00 - 16:30 1447.16 1447.67
#7 03JAN2008 09:30 - 10:00 1449.78 1447.55
#8 03JAN2008 10:00 - 10:30 1451.21 1450.66
#9 03JAN2008 10:30 - 11:00 1450.08 1452.06
#10 03JAN2008 11:00 - 11:30 1452.16 1450.01
Or
res2 <- do.call(rbind,lapply(Map(cbind, df$X1[indx],split(df[!indx,],
cumsum(indx)[!indx])), setNames, paste0('X', 1:4)))
row.names(res2) <- NULL
Upvotes: 2
Reputation: 15163
One way is with na.locf
from zoo
:
require(zoo)
df0<-cbind(df$X1,df)
df0[!is.na(df0[,3]),1]<-NA
df0[,1]<-na.locf(df0[,1])
df0<-df0[!is.na(df0[,3]),]
Which gives:
> df0
df$X1 X1 X2 X3
2 02JAN2008 09:30 - 10:00 1469.37 1467.97
3 02JAN2008 10:00 - 10:30 1459.91 1467.11
4 02JAN2008 10:30 - 11:00 1456.92 1459.76
5 02JAN2008 11:00 - 11:30 1453.48 1457.00
6 02JAN2008 15:30 - 16:00 1447.22 1444.00
7 02JAN2008 16:00 - 16:30 1447.16 1447.67
9 03JAN2008 09:30 - 10:00 1449.78 1447.55
10 03JAN2008 10:00 - 10:30 1451.21 1450.66
11 03JAN2008 10:30 - 11:00 1450.08 1452.06
12 03JAN2008 11:00 - 11:30 1452.16 1450.01
Upvotes: 3