I have a big data frame from a survey. There is some statements where I need to use revere coding, hence I need to change values in few columns. I have tried below code (where x represents the column where I want to make the changes)
df$x <- replace( df$x, 1=7, 2=6, 3=5, 5=3, 6=2, 7=1)
But this did not work. Every help is much appreciated.
If your column has only 1-7 values you can subtract those values from 8 to reverse the values.
set.seed(123)
df <- data.frame(x = sample(7, 10, replace = TRUE))
df$y <- 8 - df$x
#Or maybe more general
#df$y <- max(df$x) + 1 - df$x
df
# x y
#1 7 1
#2 7 1
#3 3 5
#4 6 2
#5 3 5
#6 2 6
#7 2 6
#8 6 2
#9 3 5
#10 5 3
You could try case_when from package dplyr. The syntax is very clean.
library(dplyr)
df %>%
mutate(x=case_when(
x == 1 ~ 7,
x == 2 ~ 6,
x == 3 ~ 5,
x == 6 ~ 2,
x == 7 ~ 1,
TRUE ~ as.numeric(x)
))
DATA
set.seed(1)
df <- data.frame(x = sample(7, 10, replace = TRUE))
df
The solution above overwrites the varaible x. To compare result, I created a new_x variable with the replaced data:
df %>%
mutate(new_x=case_when(
x == 1 ~ 7,
x == 2 ~ 6,
x == 3 ~ 5,
x == 6 ~ 2,
x == 7 ~ 1,
TRUE ~ as.numeric(x)
))
x new_x
1 1 7
2 4 4
3 7 1
4 1 7
5 2 6
6 5 5
7 7 1
8 3 5
9 6 2
10 2 6
One way you can replace values is using which:
df$x[which(df$x=1)] <- 7 # this replaces 1 with 7
Another way is to use ifelse:
df$x <- ifelse(df$x == 1,7,ifelse(df$x == 2,6,ifelse....)) # replaces 1 with 7, 2 with 6 and so on..
An option with which.max
library(dplyr)
df %>%
mutate(y = x[which.max(x)] - x + 1)
Related
I have a data frame with blocks of values with 0 and 1 and NAs, for example:
mydata <- data.frame(a = c(0,0,0,1,1,1,0,0,0,1,1,NA,NA,NA), b = c(0,0,1,1,1,1,0,0,1,1,0,NA,NA,NA))
what I want is to obtain, for each variable, the index of start and end of each block 1, this will the desired result:
mydata <- data.frame(a = c(4,6,10,11), b = c(3,6,9,10))
How can I code it?
You may try
apply(mydata, 2, function(x){
y <- rle(x == 1)
z <- c(cumsum(y$lengths)[which(y$values)], cumsum(y$lengths)[which(y$values) - 1] + 1)
return(sort(z))
})
a b
[1,] 4 3
[2,] 6 6
[3,] 10 9
[4,] 11 10
Since you stated that you only have 0,1,NA you could also use str_locate:
library(tidyverse)
map_df(mydata, ~c(t(str_locate_all(paste(., collapse = ''), '1+')[[1]])))
# A tibble: 4 x 2
a b
<int> <int>
1 4 3
2 6 6
3 10 9
4 11 10
You could also arrange it in start end format:
map_df(mydata, ~as_tibble(str_locate_all(paste(., collapse = ''), '1+')[[1]]), .id='grp')
# A tibble: 4 x 3
grp start end
<chr> <int> <int>
1 a 4 6
2 a 10 11
3 b 3 6
4 b 9 10
We can try diff + cumsum to generate grouping info and then use range to get the range of block
list2DF(
lapply(
mydata,
function(x) {
unlist(
by(
v <- which(x == 1),
cumsum(c(0, diff(v) != 1)),
range
)
)
}
)
)
which gives
a b
1 4 3
2 6 6
3 10 9
4 11 10
Another option is using aggregate
aggregate(
. ~ col,
data.frame(
which(mydata == 1, arr.ind = TRUE)
),
function(v) {
by(
v,
cumsum(c(0, diff(v) != 1)),
range
)
},
simplify = FALSE
)
which gives
col row
1 1 4, 6, 10, 11
2 2 3, 6, 9, 10
I have two columns: A and B, with values from 1 to 7 each. I need to get the maximum value between both columns EXCLUDING the value 7, how can I do that? OR In the case that A has 7 I keep the value of B, this would serve me more, for instance:
A <- c(1,1,1,3,2,4,2,5,6,7)
B <- c(7,3,6,7,4,1,6,7,3,4)
df <- data.frame(A, B)
Expected results: 1,3,6,3,4,4,6,5,6,4
One option could be:
with(df, pmax(A * (A != 7), B * (B != 7)))
[1] 1 3 6 3 4 4 6 5 6 4
To deal with missing values:
with(df, pmax(A * (A != 7), B * (B != 7), na.rm = TRUE))
Considering also negative values:
with(df, pmax(A * (A != 7)^NA, B * (B != 7)^NA, na.rm = TRUE))
Updated special thanks to dear #tmfmnk.
Maybe there is a better way of handling this problem but it instantly popped up in my mind to replace every 7 with the lowest possible value like 0 so that it does not affect the comparison. It also works with NA values.
library(dplyr)
library(purrr)
A <- c(1,1,1,3,2,4,2,5,6,7)
B <- c(7,3,6,7,4,1,6,7,3,4)
df <- tibble(A, B)
df %>%
mutate(across(everything(), ~ replace(., . == 7, 0))) %>%
mutate(ResultColumn = pmap_dbl(., ~ max(c(...), na.rm = TRUE)))
# A tibble: 10 x 3
A B ResultColumn
<dbl> <dbl> <dbl>
1 1 0 1
2 1 3 3
3 1 6 6
4 3 0 3
5 2 4 4
6 4 1 4
7 2 6 6
8 5 0 5
9 6 3 6
10 0 4 4
Here is the tidyverse example:
library(tidyverse)
A <- c(1,1,1,3,2,4,2,5,6,7)
B <- c(7,3,6,7,4,1,6,7,3,4)
df <- data.frame(A,B)
df %>%
filter(A != 7, B != 7) %>%
transform(ResultColumn = pmax(A,B))
Output:
Another base R option using pmax
> do.call(pmax, c(replace(df, df == 7, NA), na.rm = TRUE))
[1] 1 3 6 3 4 4 6 5 6 4
or
> do.call(pmax, replace(df, df == 7, -Inf))
[1] 1 3 6 3 4 4 6 5 6 4
df$max <- ifelse(pmax(df$A,df$B)==7, pmin(df$A,df$B),pmax(df$A,df$B))
I want to create a new column z based on the values of x and y. If x>y, z=y otherwise z=x.
x y
3 4
5 2
6 6
1 7
9 4
Output required:
x y z
3 4 3
5 2 2
6 6 6
1 7 1
9 4 4
You can use ifelse :
df$z <- with(df, ifelse(x > y, y, x))
#Or without with
#df$z <- ifelse(df$x > df$y, df$y, df$x)
df
# x y z
#1 3 4 3
#2 5 2 2
#3 6 6 6
#4 1 7 1
#5 9 4 4
In dplyr, you can use if_else which is same as above or case_when which is helpful when you have to list down multiple conditions.
library(dplyr)
df %>%
mutate(z = case_when(x > y ~ y,
TRUE ~x))
If I get it correctly, you are looking for minimum value out of several columns. You can use pmin function:
library(dplyr)
df <- data.frame(x = c(3,5,6,1,9),
y = c(4,2,6,7,4))
df <- df %>% mutate(z = pmin(x, y))
result:
> df
x y z
1 3 4 3
2 5 2 2
3 6 6 6
4 1 7 1
5 9 4 4
It will count minimum value in a data frame row wise and will simplify syntax if you would like to include more than 2 columns:
df <- data.frame(x = c(3, 5, 6, 1, 9),
y = c(4, 2, 6, 7, 4),
a = c(2, 5, 7, 3, 3))
df <- df %>% mutate(z = pmin(x, y, a))
result:
> df
x y a z
1 3 4 2 2
2 5 2 5 2
3 6 6 7 6
4 1 7 3 1
5 9 4 3 3
Similar to another answer but using data.table and pmin:
library(data.table)
dt <- data.table(x = c(3,5,6,1,9),
y = c(4,2,6,7,4))
dt[, z:= pmin(x,y)]
dt
# x y z
# 1: 3 4 3
# 2: 5 2 2
# 3: 6 6 6
# 4: 1 7 1
# 5: 9 4 4
Function pmin returns the parallel minima (https://www.rdocumentation.org/packages/mc2d/versions/0.1-17/topics/pmin)
Another option with fifelse in data.table
library(data.table)
setDT(dt)[, z := fifelse(x > y, y, x)]
I have the following dataframe
library(tidyverse)
x <- c(1,2,3,NA,NA,4,5)
y <- c(1,2,3,5,5,4,5)
z <- c(1,1,1,6,7,7,8)
df <- data.frame(x,y,z)
df
x y z
1 1 1 1
2 2 2 1
3 3 3 1
4 NA 5 6
5 NA 5 7
6 4 4 7
7 5 5 8
I would like to update the dataframe according to the following conditions
If z==1, update to x=1, else leave the current value for x
If z==1, update to y=2, else leave the current value for y
The following code does the job fine
df %>% mutate(x=if_else(z==1,1,x),y=if_else(z==1,2,y))
x y z
1 1 2 1
2 1 2 1
3 1 2 1
4 NA 5 6
5 NA 5 7
6 4 4 7
7 5 5 8
However, I have to add if_else statement for x and y mutate functions. This has the potential to make my code complicated and hard to read. To give you a SQL analogy, consider the following code
UPDATE df
SET x= 1, y= 2
WHERE z = 1;
I would like to achieve the following:
Specify the update condition ahead of time, so I don't have to repeat it for every mutate function
I would like to avoid using data.table or base R. I am using dplyr so I would like to stick to it for consistency
Using mutate_cond posted at dplyr mutate/replace several columns on a subset of rows we can do this:
df %>% mutate_cond(z == 1, x = 1, y = 2)
giving:
x y z
1 1 2 1
2 1 2 1
3 1 2 1
4 NA 5 6
5 NA 5 7
6 4 4 7
7 5 5 8
sqldf
Of course you can directly implement it in SQL with sqldf -- ignore the warning message that the backend RSQLite issues.
library(sqldf)
sqldf(c("update df set x = 1, y = 2 where z = 1", "select * from df"))
base R
It straight-forward in base R:
df[df$z == 1, c("x", "y")] <- list(1, 2)
library(dplyr)
df %>%
mutate(x = replace(x, z == 1, 1),
y = replace(y, z == 1, 2))
# x y z
#1 1 2 1
#2 1 2 1
#3 1 2 1
#4 NA 5 6
#5 NA 5 7
#6 4 4 7
#7 5 5 8
In base R
transform(df,
x = replace(x, z == 1, 1),
y = replace(y, z == 1, 2))
If you store the condition in a variable, you don't have to type it multiple times
condn = (df$z == 1)
transform(df,
x = replace(x, condn, 1),
y = replace(y, condn, 2))
Here is one option with map2. Loop through the 'x', 'y' columns of the dataset, along with the values to change, apply case_when based on the values of 'z' if it is TRUE, then return the new value, or else return the same column and bind the columns with the original dataset
library(dplyr)
library(purrr)
map2_df(df %>%
select(x, y), c(1, 2), ~ case_when(df$z == 1 ~ .y, TRUE ~ .x)) %>%
bind_cols(df %>%
select(z), .) %>%
select(names(df))
Or using base R, create a logical vector, use that to subset the rows of columns 'x', 'y' and update by assigning to a list of values
i1 <- df$z == 1
df[i1, c('x', 'y')] <- list(1, 2)
df
# x y z
#1 1 2 1
#2 1 2 1
#3 1 2 1
#4 NA 5 6
#5 NA 5 7
#6 4 4 7
#7 5 5 8
The advantage of both the solutions are that we can pass n number of columns with corresponding values to pass and not repeating the code
If you have an SQL background, you should really check out data.table:
library(data.table)
dt <- as.data.table(df)
set(dt, which(z == 1), c('x', 'y'), list(1, 2))
dt
# or perhaps more classic syntax
dt <- as.data.table(df)
dt
# x y z
#1: 1 1 1
#2: 2 2 1
#3: 3 3 1
#4: NA 5 6
#5: NA 5 7
#6: 4 4 7
#7: 5 5 8
dt[z == 1, `:=`(x = 1, y = 2)]
dt
# x y z
#1: 1 2 1
#2: 1 2 1
#3: 1 2 1
#4: NA 5 6
#5: NA 5 7
#6: 4 4 7
#7: 5 5 8
The last option is an update join. This is great if you have the lookup data already done upfront:
# update join:
dt <- as.data.table(df)
dt_lookup <- data.table(x = 1, y = 2, z = 1)
dt[dt_lookup, on = .(z), `:=`(x = i.x, y = i.y)]
dt
I have a column of a data frame df$c_touch:
c_touch
0
1
3
2
3
4
5
Where each number refers to a duration of time, such that 0 = 2 mins, 1 = 5 mins, 2 = 10 mins, 3=15 mins, 4=20 mins, 5=30 mins.
I'd like to add another column df$c_duration to be like
c_touch c_duration
0 2
1 5
3 15
2 10
3 15
4 20
5 30
So far I've been using a loop, which is a bit ugly/messy, and I'd rather not use it. Is there a loop-free way of adding the extra column in, particularly using dplyr mutate function (as I'm trying to rewrite all my code using dplyr)?
library(dplyr)
df %>%
mutate(c_duration = case_when(
c_touch == 0 ~ 2,
c_touch == 5 ~ 30,
TRUE ~ c_touch * 5)
)
Here is a dplyr solution:
# data.frame containing the mapping
map <- data.frame(
idx = 0:5,
val = c(2, 5, 10, 15, 20, 30))
# Sample data
df <- read.table(text =
"c_touch
0
1
3
2
3
4
5", header = T)
dplyr::left_join(df, map, by = c("c_touch" = "idx"))
# c_touch val
#1 0 2
#2 1 5
#3 3 15
#4 2 10
#5 3 15
#6 4 20
#7 5 30
You could use dplyr::case_when inside mutate:
df <- df %>%
mutate(c_duration = case_when(c_touch == 0 ~ 2,
c_touch == 1 ~ 5,
c_touch == 2 ~ 10,
c_touch == 3 ~ 15,
c_touch == 4 ~ 20,
c_touch == 5 ~ 30))