R: How to recode multiple variables at once - r

I have several variables in my dataset that need to be recoded in exactly the same way, and several other variables that need to be recoded in a different way. I tried writing a function to help me with this, but I'm having trouble.
library(dplyr)
recode_liberalSupport = function(arg1){
arg1 = recode(arg1, "1=-1;2=1;else=NA")
return(arg1)
}
liberals = c(df$var1, df$var4, df$var8)
for(i in unique(liberals)){
paste(df$liberals[i] <- sapply(liberals, FUN = recode_liberalSupport))
}
R studio works on this for about 5 minutes then gives me this error message:
Error in `$<-.data.frame`(`*tmp*`, liberals, value = c(NA_real_, NA_real_, :
replacement has 9 rows, data has 64600
In addition: Warning messages:
1: Unknown or uninitialised column: 'liberals'.
2: In df$liberals[i] <- sapply(liberals, FUN = recode_liberalSupport) :
number of items to replace is not a multiple of replacement length
Any help would be really appreciated! Thank you

This is neater I think with dplyr. Using recode correctly is a good idea. mutate_all() can be used to operate on the whole dataframe, mutate_at() on just selected variables. There are lots of ways to specify variables in dplyr.
mydata <- data.frame(arg1=c(1,2,4,5),arg2=c(1,1,2,0),arg3=c(1,1,1,1))
mydata
arg1 arg2 arg3
1 1 1 1
2 2 1 1
3 4 2 1
4 5 0 1
mydata <- mydata %>%
mutate_at(c("arg1","arg2"), funs(recode(., `1`=-1, `2`=1, .default = NaN)))
mydata
arg1 arg2 arg3
1 -1 -1 1
2 1 -1 1
3 NaN 1 1
4 NaN NaN 1
I use NaN instead of NA as it is numeric is be simpler to manage within a column of other numbers.

As always there are many ways of doing this. I don't know dplyr well enough to use that function, but this seems to be what you are looking for.
mydata <- data.frame(arg1=c(1,2,4,5),arg2=c(1,1,2,0))
mydata
arg1 arg2
1 1 1
2 2 1
3 4 2
4 5 0
Function to recode using a nested ifelse()
recode_liberalSupport <- function(var = "arg1", data=mydata) {
+ recoded <- ifelse(mydata[[var]] == 1, -1,
+ ifelse(mydata[[var]] == 2, 1, NA))
+ return(recoded)
+ }
Call the function
recode_liberalSupport(var = "arg1")
[1] -1 1 NA NA
Replace the variable arg1 with recoded values.
mydata$arg1 <- recode_liberalSupport(var = "arg1")
mydata
arg1 arg2
1 -1 1
2 1 1
3 NA 2
4 NA 0

Related

Loop to create new variable based on answers to other variables

I would like to create a new variable based on the answers to three other variables (A,B and C) in my data set. Basically my variables have three modalities : "often", "sometime" and "never". I would like to have a new variable in which each individuals has a grade ranging from 0 to 3. For each variable (A,B and C), if he answers "often", he gets 1 point otherwise, he gets 0.
My data set looks like this with "often" coded with 2 ; "sometimes" coded with 1 and "never" coded with 0.
A <- c(2,1,1,NA, 0,2)
B <- c(2,2,0,1,2,NA)
C <- c(2,1,NA,2,1,0)
data <- data.frame(A,B,C)
I know I could use case_when but it is a rather unwieldy solution. I was thinking of a loop but I never used loops in R. Can you help me with this loop?
Do you mean something like this?
Update: thanks to markus. His solution (rowSums(data == 2, na.rm = TRUE))is much better than my original
base R
data$points = rowSums(data == 2, na.rm = TRUE)
dplyr
library(dplyr)
data %>% mutate(point = rowSums(data == 2, na.rm = TRUE))
data.table
library(data.table)
setDT(data)
data[, points:=rowSums(data == 2, na.rm = TRUE)]
Output
> data
A B C points
1 2 2 2 3
2 1 2 1 1
3 1 0 NA 0
4 NA 1 2 1
5 0 2 1 1
6 2 NA 0 1

How to assign values to variable in R [duplicate]

I have several variables in my dataset that need to be recoded in exactly the same way, and several other variables that need to be recoded in a different way. I tried writing a function to help me with this, but I'm having trouble.
library(dplyr)
recode_liberalSupport = function(arg1){
arg1 = recode(arg1, "1=-1;2=1;else=NA")
return(arg1)
}
liberals = c(df$var1, df$var4, df$var8)
for(i in unique(liberals)){
paste(df$liberals[i] <- sapply(liberals, FUN = recode_liberalSupport))
}
R studio works on this for about 5 minutes then gives me this error message:
Error in `$<-.data.frame`(`*tmp*`, liberals, value = c(NA_real_, NA_real_, :
replacement has 9 rows, data has 64600
In addition: Warning messages:
1: Unknown or uninitialised column: 'liberals'.
2: In df$liberals[i] <- sapply(liberals, FUN = recode_liberalSupport) :
number of items to replace is not a multiple of replacement length
Any help would be really appreciated! Thank you
This is neater I think with dplyr. Using recode correctly is a good idea. mutate_all() can be used to operate on the whole dataframe, mutate_at() on just selected variables. There are lots of ways to specify variables in dplyr.
mydata <- data.frame(arg1=c(1,2,4,5),arg2=c(1,1,2,0),arg3=c(1,1,1,1))
mydata
arg1 arg2 arg3
1 1 1 1
2 2 1 1
3 4 2 1
4 5 0 1
mydata <- mydata %>%
mutate_at(c("arg1","arg2"), funs(recode(., `1`=-1, `2`=1, .default = NaN)))
mydata
arg1 arg2 arg3
1 -1 -1 1
2 1 -1 1
3 NaN 1 1
4 NaN NaN 1
I use NaN instead of NA as it is numeric is be simpler to manage within a column of other numbers.
As always there are many ways of doing this. I don't know dplyr well enough to use that function, but this seems to be what you are looking for.
mydata <- data.frame(arg1=c(1,2,4,5),arg2=c(1,1,2,0))
mydata
arg1 arg2
1 1 1
2 2 1
3 4 2
4 5 0
Function to recode using a nested ifelse()
recode_liberalSupport <- function(var = "arg1", data=mydata) {
+ recoded <- ifelse(mydata[[var]] == 1, -1,
+ ifelse(mydata[[var]] == 2, 1, NA))
+ return(recoded)
+ }
Call the function
recode_liberalSupport(var = "arg1")
[1] -1 1 NA NA
Replace the variable arg1 with recoded values.
mydata$arg1 <- recode_liberalSupport(var = "arg1")
mydata
arg1 arg2
1 -1 1
2 1 1
3 NA 2
4 NA 0

Complete.cases used on list of data frames

I'm trying to remove all the NA values from a list of data frames. The only way I have got it to work is by cleaning the data with complete.cases in a for loop. Is there another way of doing this with lapply as I had been trying for a while to no avail. Here is the code that works.
I start with
data_in <- lapply (file_name,read.csv)
Then have:
clean_data <- list()
for (i in seq_along(id)) {
clean_data[[i]] <- data_in[[i]][complete.cases(data_in[[i]]), ]
}
But what I tried to get to work was using lapply all the way like this.
comp <- lapply(data_in, complete.cases)
clean_data <- lapply(data_in, data_in[[id]][comp,])
Which returns this error "Error in [.default(xj, i) : invalid subscript type 'list' "
What I'd like to know is some alternatives or if I was going about this right. And why didn't the last example not work?
Thank you so much for your time. Have a nice day.
I'm not sure what you expected with
clean_data <- lapply(data_in, data_in[[id]][comp,])
The second parameter to lapply should be a proper function to which each member of the data_in list will be passed one at a time. Your expression data_in[[id]][comp,] is not a function. I'm not sure where you expected id to come from, but lapply does not create magic variables for you like that. Also, at this point comp is now a list itself of indices. You are making no attempt to iterate over this list in sync with your data_in list. If you wanted to do it in two separate steps, a more appropriate approach would be
comp <- lapply(data_in, complete.cases)
clean_data <- Map(function(d,c) {d[c,]}, data_in, comp)
Here we use Map to iterate over the data_in and comp lists simultaneously. They each get passed in to the function as a parameter and we can do the proper extraction that way. Otherwise, if we wanted to do it in one step, we could do
clean_data <- lapply(data_in, function(x) x[complete.cases(x),])
welcome to SO, please provide some working code next time
here is how i would do it with na.omit (since complete.cases only returns a logical)
(dat.l <- list(dat1 = data.frame(x = 1:2, y = c(1, NA)),
dat2 = data.frame(x = 1:3, y = c(1, NA, 3))))
# $dat1
# x y
# 1 1 1
# 2 2 NA
#
# $dat2
# x y
# 1 1 1
# 2 2 NA
# 3 3 3
Map(na.omit, dat.l)
# $dat1
# x y
# 1 1 1
#
# $dat2
# x y
# 1 1 1
# 3 3 3
Do you mean like the below?
> lst
$a
a
1 1
2 2
3 NA
4 3
5 4
$b
b
1 1
2 NA
3 2
4 3
5 4
$d
d e
1 NA 1
2 NA 2
3 3 3
4 4 NA
5 5 NA
> f <- function(x) x[complete.cases(x),]
> lapply(lst, f)
$a
[1] 1 2 3 4
$b
[1] 1 2 3 4
$d
d e
3 3 3
file_name[complete.cases(file_name), ]
complete.cases() returns only a logical value. This should do the job and returns only the rows with no NA values.

R ddply, applying if and ifelse functions

I'm trying to apply a function to a dataframe using ddply from the plyr package, but I'm getting some results that I don't understand. I have 3 questions about the
results
Given:
mydf<- data.frame(c(12,34,9,3,22,55),c(1,2,1,1,2,2)
, c(0,1,2,1,1,2))
colnames(mydf)[1] <- 'n'
colnames(mydf)[2] <- 'x'
colnames(mydf)[3] <- 'x1'
mydf looks like this:
n x x1
1 12 1 0
2 34 2 1
3 9 1 2
4 3 1 1
5 22 2 1
6 55 2 2
Question #1
If I do:
k <- function(x) {
mydf$z <- ifelse(x == 1, 0, mydf$n)
return (mydf)
}
mydf <- ddply(mydf, c("x") , .fun = k, .inform = TRUE)
I get the following error:
Error in `$<-.data.frame`(`*tmp*`, "z", value = structure(c(12, 34, 9, :
replacement has 3 rows, data has 6
Error: with piece 1:
n x x1
1 12 1 0
2 9 1 2
3 3 1 1
I get this error regardless of whether I specify the variable to split by as c("x"), "x", or .(x). I don't understand why I'm getting this error message.
Question #2
But, what I really want to do is set up an if/else function because my dataset has variables x1, x2, x3, and x4 and I want to take those variables into account as well. But when I try something simple such as:
j <- function(x) {
if(x == 1){
mydf$z <- 0
} else {
mydf$z <- mydf$n
}
return(mydf)
}
mydf <- ddply(mydf, x, .fun = j, .inform = TRUE)
I get:
Warning messages:
1: In if (x == 1) { :
the condition has length > 1 and only the first element will be used
2: In if (x == 1) { :
the condition has length > 1 and only the first element will be used
Question #3
I'm confused about to use function() and when to use function(x). Using function() for either j() or k() gives me a different error:
Error in .fun(piece, ...) : unused argument (piece)
Error: with piece 1:
n x x1 z
1 12 1 0 12
2 9 1 2 9
3 3 1 1 3
4 12 1 0 12
5 9 1 2 9
6 3 1 1 3
7 12 1 0 12
8 9 1 2 9
9 3 1 1 3
10 12 1 0 12
11 9 1 2 9
12 3 1 1 3
where column z is not correct. Yet I see a lot of functions written as function().
I sincerely appreciate any comments that can help me out with this
There's a lot that needs explaining here. Let's start with the simplest case. In your first example, all you need is:
mydf$z <- with(mydf,ifelse(x == 1,0,n))
An equivalent ddply solution might look like this:
ddply(mydf,.(x),transform,z = ifelse(x == 1,0,n))
Probably your biggest source of confusion is that you seem to not understand what is being passed as arguments to functions within ddply.
Consider your first attempt:
k <- function(x) {
mydf$z <- ifelse(x == 1, 0, mydf$n)
return (mydf)
}
The way ddply works is that it splits mydf up into several, smaller data frame, based on the values in the column x. That means that each time ddply calls k, the argument passed to k is a data frame. Specifically, a subset of you primary data frame.
So within k, x is a subset of mydf, with all the columns. You should not be trying to modify mydf from within k. Modify x, and then return the modified version. (If you must, but the options I displayed above are better.) So we might re-write your k like this:
k <- function(x) {
x$z <- ifelse(x$x == 1, 0, x$n)
return (x)
}
Note that you've created some confusing stuff by using x as both an argument to k and as the name of one of our columns.

R: How can I sum across variables, within cases, while counting NA as zero

Fake data for illustration:
df <- data.frame(a=c(1,2,3,4,5), b=(c(2,2,2,2,NA)),
c=c(NA,2,3,4,5)))
This would get me the answer I want IF it weren't for the NA values:
df$count <- with(df, (a==1) + (b==2) + (c==3))
Also, would there be an even more elegant way if I was only interested in, e.g. variables==2?
df$count <- with(df, (a==2) + (b==2) + (c==2))
Many thanks!
The following works for your specific example, but I have a suspicion that your real use case is more complicated:
df$count <- apply(df,1,function(x){sum(x == 1:3,na.rm = TRUE)})
> df
a b c count
1 1 2 NA 2
2 2 2 2 1
3 3 2 3 2
4 4 2 4 1
5 5 NA 5 0
but this general approach should work. For instance, your second example would be something like this:
df$count <- apply(df,1,function(x){sum(x == 2,na.rm = TRUE)})
or more generally you could allow yourself to pass in a variable for the comparison:
df$count <- apply(df,1,function(x,compare){sum(x == compare,na.rm = TRUE)},compare = 1:3)
Another way is to subtract your target vector from each row of your data.frame, negate and then do rowSums with na.rm=TRUE:
target <- 1:3
rowSums(!(df-rep(target,each=nrow(df))),na.rm=TRUE)
[1] 2 1 2 1 0
target <- rep(2,3)
rowSums(!(df-rep(target,each=nrow(df))),na.rm=TRUE)
[1] 1 3 1 1 0

Resources