fill missing columns with NA while extracting from a data.frame - r

I have a function that takes as input a dataframe with certain columns
columns =['a', 'b',...,'z']
Now I have a dataframe DF with only few of these columns DF_columns = ['f', 'u', 'z']
How can I create a dataframe that has all the columns with value NA if the columns are not in DF and that coincides with DF on the columns ['f', 'u', 'z']
Example:
d = data.frame('g'=c(1,2,3), 's' = c(4,2,3))
columns = letters[1:21]
columns
[1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t"
[21] "u"
> d
g s
1 1 4
2 2 2
3 3 3
>

x.or.na <- function(x, df) if (x %in% names(df)) df[[x]] else NA
as.data.frame(Map(x.or.na, columns, list(d)))

set.seed(42)
DF <- setNames(as.data.frame(matrix(sample(1:15, 15, replace=TRUE), ncol=3)), c('f', 'u', 'z') )
DF
# f u z
#1 14 8 7
#2 15 12 11
#3 5 3 15
#4 13 10 4
#5 10 11 7
res <- do.call(`data.frame`,lapply(split(letters[4:26], letters[4:26]),
function(x){x1 <- match(x, colnames(DF)); if(!is.na(x1)) DF[,x1] else NA}))
res
# d e f g h i j k l m n o p q r s t u v w x y z
#1 NA NA 14 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 8 NA NA NA NA 7
#2 NA NA 15 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 12 NA NA NA NA 11
#3 NA NA 5 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 3 NA NA NA NA 15
#4 NA NA 13 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 10 NA NA NA NA 4
#5 NA NA 10 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 11 NA NA NA NA 7
Using dplyr
library(dplyr)
DF %>%
do({x1 <-data.frame(., setNames(as.list(rep(NA, sum(!letters[4:26] %in% names(DF)))),
setdiff(letters[4:26], names(DF))))
x1[,order(colnames(x1))] })
# d e f g h i j k l m n o p q r s t u v w x y z
#1 NA NA 14 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 8 NA NA NA NA 7
#2 NA NA 15 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 12 NA NA NA NA 11
#3 NA NA 5 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 3 NA NA NA NA 15
#4 NA NA 13 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 10 NA NA NA NA 4
#5 NA NA 10 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 11 NA NA NA NA 7

This is quite easy (in terms of syntax) and efficient (in terms of speed) using the data.table package:
require(data.table) ## 1.9.2+
setDT(d)[, setdiff(columns, names(d)) := NA] ## (1)
setcolorder(d, columns) ## (2)
setDF(d) ## (3)
setDT converts d to a data.table, after which we use the := operator to create new columns by reference. There are many ways to use :=, but highlighted here is the use case LHS := RHS. Here LHS is a vector of column names and RHS is the value. NA is provided only once on the RHS, which gets automatically recycled for all other columns. Note that NA by default is logical type in R.
If required you can reorder the columns of d in the same order as columns using setcolorder.
Again, if necessary, you can convert the data.table back to a data.frame, using the function setDF, which again modifies the object by reference. But it's available in the development version v1.9.3 only for now.

Here are a few methods and their timings.
createDF1 <- function(colVec, data)
{
m <- matrix(, nrow = nrow(data), ncol = length(colVec),
dimnames = list(NULL, colVec))
m[, names(data)] <- as.matrix(data)
data.frame(apply(m, 2, as.numeric))
}
createDF2 <- function(colVec, data)
{
rr <- setNames(rep(list(rep(NA_integer_, nrow(data))), length(colVec)), .
nm = colVec)
rr[match(names(data), colVec)] <- data
as.data.frame(rr)
}
createDF3 <- function(colVec, data)
{
rr <- setNames(replicate(length(colVec),
list(rep(NA_integer_, nrow(data)))),
nm = colVec)
rr[match(names(d), colVec)] <- data
as.data.frame(rr)
}
Create a 3,000,000 x 3 data frame to test on:
columns <- letters[1:21]
d <- data.frame(g = 1:3e6L, s = 1:3e6L, j = 1:3e6L)
Run some tests:
system.time({ createDF1(columns, d) })
# user system elapsed
# 5.022 1.023 6.054
system.time({ createDF2(columns, d) })
# user system elapsed
# 0.007 0.004 0.011
system.time({ createDF3(columns, d) })
# user system elapsed
# 0.105 0.077 0.183
Of these three, it looks like rep(list(rep(NA_integer_, nrow(data))), length(columns)) is the way to go, and replace values from that.

Setup:
set.seed(1)
DF_all <- setNames(data.frame(matrix(rnorm(5*26), nrow=5, ncol=26)), letters)
DF <- DF_all[, c('f','u','z')]
Create a new empty dataframe and populate with your columns:
DF2 <- setNames(data.frame(matrix(nrow=5, ncol=26)), letters)
DF2[, c('f','u','z')] <- DF[, c('f','u','z')]
Result:
> DF2
a b c d e f g h i j k l m n o p q r s t u v w x y z
1 NA NA NA NA NA -0.05612874 NA NA NA NA NA NA NA NA NA NA NA NA NA NA -0.62036668 NA NA NA NA 0.71266631
2 NA NA NA NA NA -0.15579551 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 0.04211587 NA NA NA NA -0.07356440
3 NA NA NA NA NA -1.47075238 NA NA NA NA NA NA NA NA NA NA NA NA NA NA -0.91092165 NA NA NA NA -0.03763417
4 NA NA NA NA NA -0.47815006 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 0.15802877 NA NA NA NA -0.68166048
5 NA NA NA NA NA 0.41794156 NA NA NA NA NA NA NA NA NA NA NA NA NA NA -0.65458464 NA NA NA NA -0.32427027

[<- could be used to fill up the missing columns with NA.
`[<-`(d,, setdiff(columns, names(d)), NA)[columns]
#`[<-`(d,, columns[!columns %in% names(d)], NA)[columns] #Alternative
# a b c d e f g h i j k l m n o p q r s t u
#1 NA NA NA NA NA NA 1 NA NA NA NA NA NA NA NA NA NA NA 4 NA NA
#2 NA NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA NA NA 2 NA NA
#3 NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA NA NA 3 NA NA
Or directly adding the missing columns to the original data.frame
d[columns[!columns %in% names(d)]] <- NA
d[columns]
# a b c d e f g h i j k l m n o p q r s t u
#1 NA NA NA NA NA NA 1 NA NA NA NA NA NA NA NA NA NA NA 4 NA NA
#2 NA NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA NA NA 2 NA NA
#3 NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA NA NA 3 NA NA
Or in a function:
f <- function(DF, COL) {
d[columns[!columns %in% names(d)]] <- NA
d[columns]
}
f(d, columns)
# a b c d e f g h i j k l m n o p q r s t u
#1 NA NA NA NA NA NA 1 NA NA NA NA NA NA NA NA NA NA NA 4 NA NA
#2 NA NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA NA NA 2 NA NA
#3 NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA NA NA 3 NA NA
Data
d <- data.frame('g'=c(1,2,3), 's' = c(4,2,3))
columns <- letters[1:21]

Related

Add empty columns to obtain certain amount of columns to existing dataframe

I have a dataframe with variable number of F, F can go from 1 to X
df1 <- data.frame(F1=c(1,5,NA,9),F2=c(2,5,"a",NA),F3=c(1,NA,"o",NA))
df1
F1 F2 F3
1 1 2 1
2 5 5 <NA>
3 NA a o
4 9 <NA> <NA>
but I need my data frame to have certain amount of columns, lets say 30.
If X is smaller then 30 then I need the amount of columns needed (with NA values) to be added to make 30 columns.
> df1
F1 F2 F3 F4......F30
1 1 2 1 <NA>.....<NA>
2 5 5 <NA> <NA>.....<NA>
3 NA a o <NA>.....<NA>
4 9 <NA> <NA> <NA>.....<NA>
Thanks!
We may use setdiff on the names of the dataset and assign those to NA
df1[setdiff(paste0("F", 1:30), names(df1))] <- NA
-output
> df1
F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30
1 1 2 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
2 5 5 <NA> NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
3 NA a o NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
4 9 <NA> <NA> NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
I think it can first create an empty data.frame with X or 30 columns, than replace selected columns with your data.frame.
libary(dplyr)
library(purrr)
df1<-tibble(map_dfc(1:30, ~rep(NA, nrow(df1)))) %>%
set_names(~paste0("F", 1:30)) %>%
coalesce(., df1)

Insert NA elements in vector

I have a vector:
x <- c(1,2,3,4)
I would like to add 23 NA elements before each element of x
Maybe like this?
c(sapply(x, function(x) c(rep(NA,23),x)))
We can do this with vectorization
replace(rep(NA, 23*length(x) + length(x)), rep(c(FALSE, TRUE), c(23, 1)), x)
#[1] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
#[43] NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA NA NA NA
#[85] NA NA NA NA NA NA NA NA NA NA NA 4
Or another option is to create a matrix, replace the last row with 'x' and convert it to vector
m1 <- matrix(rep(rep(NA, 24), length(x)), nrow = length(x))
m1[,24] <- x
c(t(m1))

Subsets defined by k-way combinations of factors in R

I would like to apply a function (will be a custom function, but for simplicity I will say it is mean) to subgroups defined by combinations of factors. I have 20 factors, but I would like to consider, say, subgroups defined by all combinations of 1,2,3,...,k of the factors.
Here is an example for k=3
N = 100
test_data <- data.frame( factorA = factor(sample(1:4, replace = TRUE, size = N)), factorB = factor(sample(1:2, replace = TRUE, size = N)), factorC = factor(sample(1:2, replace = TRUE, size = N)), var = rnorm(n = N))
#1-way subsets
mean(test_data$var[test_data$factorA == "1"])
mean(test_data$var[test_data$factorA == "2"])
mean(test_data$var[test_data$factorA == "3"])
mean(test_data$var[test_data$factorA == "4"])
mean(test_data$var[test_data$factorB == "1"])
#and so forth...
#2-way subsets
mean(test_data$var[test_data$factorA == "1" & test_data$factorB == "1" ])
mean(test_data$var[test_data$factorA == "1" & test_data$factorB == "2" ])
mean(test_data$var[test_data$factorA == "1" & test_data$factorC == "1" ])
#and so forth...
#3-way subsets
mean(test_data$var[test_data$factorA == "1" & test_data$factorB == "1" & test_data$factorC == "1" ])
mean(test_data$var[test_data$factorA == "1" & test_data$factorB == "1" & test_data$factorC == "2" ])
#and so forth...
For each combinations of k factors, compute the mean of var for all combinations of levels for these k factors. It would be best if the output is then labeled the given combination of factors/levels that defines the subset.
It seems that expand.grid and/or combn should be useful, but not sure how to use them in this situation.
To calculate the mean of var for all combinations of all three factors you can use the data.table by argument:
library(data.table)
N = 100
test_data <- data.frame(factorA = factor(sample(1:4, replace = TRUE, size = N)),
factorB = factor(sample(1:2, replace = TRUE, size = N)),
factorC = factor(sample(1:2, replace = TRUE, size = N)), var = rnorm(n = N))
setDT(test_data)
test_data[, .(mean_var = mean(var, na.rm = TRUE)),
by = .(factorA, factorB, factorC)]
Which gives this output:
factorA factorB factorC mean_var
1: 1 1 1 -0.304218613
2: 1 1 2 -0.122405096
3: 1 2 1 0.532219871
4: 1 2 2 -0.679400706
5: 2 1 1 0.006901209
6: 2 1 2 0.605850466
7: 2 2 1 -0.083305497
8: 2 2 2 -0.408660971
9: 3 1 1 -0.362234218
10: 3 1 2 -0.368472511
11: 3 2 1 0.243274183
12: 3 2 2 0.119927615
13: 4 1 1 -0.517337915
14: 4 1 2 -0.790908511
15: 4 2 1 -0.077665828
16: 4 2 2 -0.295695277
Updated with example data containing 20 factor columns (with two to four levels each). All possible combinations of three factors (i.e. columns) are generated (6480) and for each combination the mean_var for each unique combination of factor levels is calculated:
library(data.table)
# Generate example data
N = 100
dt <- dcast(rbindlist(lapply(seq(1:20), function(x) {
dt_tmp <- data.table(id = 1:N, factor = paste0("factor", LETTERS[x]),
value = sample(1:sample(2:4, 1), replace = TRUE, size = N))
})), id~factor)[, ":="(var = rnorm(n = N), id = NULL)]
# Generate all combinations of three out of the 20 factors (20*19*18 = 6840)
factors <- colnames(dt[, 1:20])
tests <- CJ(k1 = factors, k2 = factors, k3 = factors)[k1 != k2 & k1 != k3 & k2 != k3]
# Iterate over every row of tests and calculate mean_var for each unique
# combination of the three factors (this takes time - output ~ 170000 rows)
dt_out <- rbindlist(lapply(seq(1:nrow(tests)), function(x) {
dt[, .(mean_var = mean(var, na.rm = TRUE)),
by = c(tests[x, k1], tests[x, k2], tests[x, k3])]
}), use.names = TRUE, fill = TRUE)
The output looks like this:
> head(out_dt, 30)
factorA factorB factorC mean_var factorD factorE factorF factorG factorH factorI factorJ factorK factorL factorM factorN factorO factorP factorQ factorR factorS factorT
1: 1 2 3 -0.595391823 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
2: 2 1 1 -0.049915238 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
3: 2 2 4 0.087206182 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
4: 2 1 2 0.010622079 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
5: 1 2 1 0.277414685 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
6: 1 1 3 0.366482963 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
7: 2 2 3 0.017438655 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
8: 2 2 1 -1.116071505 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
9: 2 1 4 1.371340706 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
10: 2 2 2 0.045354904 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
11: 1 2 2 0.644926008 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
12: 1 2 4 -0.121767568 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
13: 1 1 2 0.261070274 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
14: 2 1 3 -0.506061865 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
15: 1 1 4 -0.075228598 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
16: 1 1 1 0.333514316 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
17: 1 2 NA -0.185980008 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
18: 2 1 NA -0.113793548 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
19: 2 2 NA 0.015100176 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
20: 1 2 NA 0.484182038 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
21: 1 1 NA -0.123811140 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
22: 1 1 NA 0.543852715 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
23: 2 2 NA -0.267626769 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
24: 2 1 NA 0.133316773 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
25: 1 2 NA 0.538964320 NA 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
26: 2 1 NA 0.006298113 NA 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
27: 2 2 NA 0.010152043 NA 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
28: 2 1 NA 0.011377912 NA 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
29: 1 1 NA 0.504610954 NA 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
30: 2 2 NA -0.311834384 NA 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
factorA factorB factorC mean_var factorD factorE factorF factorG factorH factorI factorJ factorK factorL factorM factorN factorO factorP factorQ factorR factorS factorT

R name colnames and rownames in list of data.frames with lapply

I'm pretty frustrated because I dont know how I achieve the naming of the columns and rows in a list of data.frames. I mean I want to avoid using a loop. So I figured I could use just lapply.
Ok at first I have the following list:
>a
$nem.greedyMAP.FALSE.POS
X1 X2 X3 X4 X5 X6 X7 X8 X9 X10
1 NA NA NA NA NA NA NA NA NA NA
2 NA NA NA NA NA NA NA NA NA NA
3 NA NA NA NA NA NA NA NA NA NA
4 NA NA NA NA NA NA NA NA NA NA
5 NA NA NA NA NA NA NA NA NA NA
6 NA NA NA NA NA NA NA NA NA NA
7 NA NA NA NA NA NA NA NA NA NA
8 NA NA NA NA NA NA NA NA NA NA
9 NA NA NA NA NA NA NA NA NA NA
10 NA NA NA NA NA NA NA NA NA NA
$nem.greedyMAP.FALSE.NEG
X1 X2 X3 X4 X5 X6 X7 X8 X9 X10
1 NA NA NA NA NA NA NA NA NA NA
2 NA NA NA NA NA NA NA NA NA NA
3 NA NA NA NA NA NA NA NA NA NA
4 NA NA NA NA NA NA NA NA NA NA
5 NA NA NA NA NA NA NA NA NA NA
6 NA NA NA NA NA NA NA NA NA NA
7 NA NA NA NA NA NA NA NA NA NA
8 NA NA NA NA NA NA NA NA NA NA
9 NA NA NA NA NA NA NA NA NA NA
10 NA NA NA NA NA NA NA NA NA NA
Of course this list is much bigger, otherwise I wouldnt be worth the trouble.
However I want to rename the columns and rows for all data.frames the same.
So I though I could use:
lapply(a, function(x) {colnames(x) <- paste("col",1:10,sep="")})
But nothing happens. How could I achieve this. Or is lapply the wrong way?
Thanks
I'll prefer setNames in this case
set.seed(1)
datalist <- list(dat1 = data.frame(A = 1:10, B = rnorm(10)),
dat2 = data.frame(C = 100:109, D = rnorm(10))
)
lapply(datalist, names)
## $dat1
## [1] "A" "B"
## $dat2
## [1] "C" "D"
datalist <- lapply(datalist, setNames, paste0("col", 1:2))
lapply(datalist, names)
## $dat1
## [1] "col1" "col2"
## $dat2
## [1] "col1" "col2"
EDIT
A more general solution to modify rownames and colnames within a list
lapply(datalist, "colnames<-", paste0("col", 1:2))
lapply(datalist, "rownames<-", letters[1:10])
You need to remember that the object x inside the lapply is not the original object, but a copy. Changing the colnames of the copy does not impact the original object. You need to return x in order to get a new copy of the object that includes the new names.
new_obj = lapply(a, function(x) {
colnames(x) <- paste("col",1:10,sep="")
return(x)
})

How to populate matrix with values from another matrix in R?

How do you populate an empty matrix with the values of another matrix?
The empty matrix:
> m1 <- matrix(ncol=8, nrow=8)
> rownames(m1) <- c('a','b','c','d','e','f','g','h')
> colnames(m1) <- c('a','b','c','d','e','f','g','h')
> m1
a b c d e f g h
a NA NA NA NA NA NA NA NA
b NA NA NA NA NA NA NA NA
c NA NA NA NA NA NA NA NA
d NA NA NA NA NA NA NA NA
e NA NA NA NA NA NA NA NA
f NA NA NA NA NA NA NA NA
g NA NA NA NA NA NA NA NA
h NA NA NA NA NA NA NA NA
The matrix with the values to populate the empty matrix:
> m2 <- matrix(ncol=4, nrow=4)
> rownames(m2) <- c('b','e','h','x')
> colnames(m2) <- c('b','e','h','x')
> m2[,'b'] <- c(1,2,3,1)
> m2[,'e'] <- c(2,1,1,5)
> m2[,'h'] <- c(3,1,3,5)
> m2[,'x'] <- c(1,5,5,1)
> m2
b e h x
b 1 2 3 1
e 2 1 1 5
h 3 1 3 5
x 1 5 5 1
How do you merge the two matrixes to get this result:
a b c d e f g h
a NA NA NA NA NA NA NA NA
b NA 1 NA NA 2 NA NA 3
c NA NA NA NA NA NA NA NA
d NA NA NA NA NA NA NA NA
e NA 2 NA NA 1 NA NA 1
f NA NA NA NA NA NA NA NA
g NA NA NA NA NA NA NA NA
h NA 3 NA NA 1 NA NA 3
Edit: added row/col x in m2, which is not in m1
Find the column (row) names that both matrices have in common
cols <- colnames(m1)[colnames(m1) %in% colnames(m2)]
rows <- rownames(m1)[rownames(m1) %in% rownames(m2)]
Then assign the appropriate values from m2 to m1
m1[rows, cols] <- m2[rows, cols]
m1
# a b c d e f g h
#a NA NA NA NA NA NA NA NA
#b NA 1 NA NA 2 NA NA 3
#c NA NA NA NA NA NA NA NA
#d NA NA NA NA NA NA NA NA
#e NA 2 NA NA 1 NA NA 1
#f NA NA NA NA NA NA NA NA
#g NA NA NA NA NA NA NA NA
#h NA 3 NA NA 1 NA NA 3

Resources