I'm seeing some unexpected behavior with dplyr. I have a specific use case but I will setup a dummy problem to illustrate my point. Why does this work,
library(dplyr)
temp <- bind_cols(mtcars %>% select(-mpg), mtcars %>% select(mpg))
head(temp)
cyl disp hp drat wt qsec vs am gear carb mpg
6 160.0 110 3.90 2.620 16.46 0 1 4 4 21.0
6 160.0 110 3.90 2.875 17.02 0 1 4 4 21.0
But not this,
library(dplyr)
temp <- mtcars %>% bind_cols(. %>% select(-mpg), . %>% select(mpg))
Error in cbind_all(x) : Argument 2 must be length 1, not 32
Thanks for the help.
You need to wrap your function with {} to pipe mtcars into a function within another function like the following:
library(dplyr)
temp1 = mtcars %>% {bind_cols(select(., -mpg), select(., mpg))}
temp2 = bind_cols(mtcars %>% select(-mpg), mtcars %>% select(mpg))
# > identical(temp1, temp2)
# [1] TRUE
Another solution:
myfun <- function(x) {
bind_cols(x %>% select(-mpg), x %>% select(mpg))
}
temp <- mtcars %>% myfun
Related
I need to rename multiple variables using a replacement dataframe. This replacement dataframe also includes regex. I would like to use a similar solution proposed here, .e.g
df %>% rename_with(~ newnames, all_of(oldnames))
MWE:
df <- mtcars[, 1:5]
# works without regex
replace_df_1 <- tibble::tibble(
old = df %>% colnames(),
new = df %>% colnames() %>% toupper()
)
df %>% rename_with(~ replace_df_1$new, all_of(replace_df_1$old))
# with regex
replace_df_2 <- tibble::tibble(
old = c("^m", "cyl101|cyl", "disp", "hp", "drat"),
new = df %>% colnames() %>% toupper()
)
old new
<chr> <chr>
1 ^m MPG
2 cyl101|cyl CYL
3 disp DISP
4 hp HP
5 drat DRAT
# does not work
df %>% rename_with(~ replace_df_2$new, all_of(replace_df_2$old))
df %>% rename_with(~ matches(replace_df_2$new), all_of(replace_df_2$old))
EDIT 1:
The solution of #Mael works in general, but there seems to be index issue, e.g. consider the following example
replace_df_2 <- tibble::tibble(
old = c("xxxx", "cyl101|cyl", "yyy", "xxx", "yyy"),
new = mtcars[,1:5] %>% colnames() %>% toupper()
)
mtcars[, 1:5] %>%
rename_with(~ replace_df_2$new, matches(replace_df_2$old))
Results in
mpg MPG disp hp drat
<dbl> <dbl> <dbl> <dbl> <dbl>
1 21 6 160 110 3.9
meaning that the rename_with function correctly finds the column, but replaces it with the first item in the replacement column. How can we tell the function to take the respective row where a replacement has been found?
So in this example (edit 1), I only want to substitute the second column with "CYL", the rest should be left untouched. The problem is that the function takes the first replacement (MPG) instead of the second (CYL).
Thank you for any hints!
matches should be on the regex-y column:
df %>%
rename_with(~ replace_df_2$new, matches(replace_df_2$old))
MPG CYL DISP HP DRAT
Mazda RX4 21.0 6 160.0 110 3.90
Mazda RX4 Wag 21.0 6 160.0 110 3.90
Datsun 710 22.8 4 108.0 93 3.85
Hornet 4 Drive 21.4 6 258.0 110 3.08
Hornet Sportabout 18.7 8 360.0 175 3.15
Valiant 18.1 6 225.0 105 2.76
#...
If the task is simply to set all col names to upper-case, then this works:
sub("^(.+)$", "\\U\\1", colnames(df), perl = TRUE)
[1] "MPG" "CYL" "DISP" "HP" "DRAT"
In dplyr:
df %>%
rename_with( ~sub("^(.+)$", "\\U\\1", colnames(df), perl = TRUE))
I found a solution using the idea of non standard evaluation from this question and #Maƫl's answer.
Using map_lgl we create a logical vector that returns TRUE if the column in replace_df_2$old can be found inside the dataframe df. Then we pass this logical vector to replace_df_2$new to get the correct replacement.
df <- mtcars[, 1:5]
df %>%
rename_with(.fn = ~replace_df_2$new[map_lgl(replace_df_2$old,~ any(str_detect(., names(df))))],
.cols = matches(replace_df_2$old))
Result:
mpg CYL disp hp drat
Mazda RX4 21.0 6 160.0 110 3.90
Suppose I am working with the mtcars dataset, and I would like to add:
1 to all values in the column: mpg
2 to all values in the column: cyl
3 to all values in the column: disp
I would like to keep all columns in mtcars, and refer to the columns by their names rather than their index.
Here's my current attempt:
library("tidyverse")
library("rlang")
data(mtcars)
mtcars_colnames <- quo(c("mpg", "cyl", "disp"))
num <- c(1, 2, 3)
mtcars %>% mutate(across(!!! mtcars_colnames, function(x) {x + num[col(.)]}))
I'm stuck on how to dynamically add (1,2,3) to columns (mpg, cyl, disp).
Thanks in advance.
We could change the input by passing just a vector of strings instead of quosures and a named vector for 'num', then use the cur_column inside the across to match with the named vector of 'num', get the corresponding value and do the addition
library(dplyr)
mtcars_colnames <- c("mpg", "cyl", "disp")
num <- setNames(c(1, 2, 3), mtcars_colnames)
mtcars1 <- mtcars %>%
mutate(across(all_of(mtcars_colnames), ~ num[cur_column()] + .))
-check the output
# // old data
mtcars %>%
select(all_of(mtcars_colnames)) %>%
slice_head(n = 5)
# mpg cyl disp
#Mazda RX4 21.0 6 160
#Mazda RX4 Wag 21.0 6 160
#Datsun 710 22.8 4 108
#Hornet 4 Drive 21.4 6 258
#Hornet Sportabout 18.7 8 360
# // new data
mtcars1 %>%
select(all_of(mtcars_colnames)) %>%
slice_head(n = 5)
# mpg cyl disp
#Mazda RX4 22.0 8 163
#Mazda RX4 Wag 22.0 8 163
#Datsun 710 23.8 6 111
#Hornet 4 Drive 22.4 8 261
#Hornet Sportabout 19.7 10 363
Or if we prefer to pass a unnamed 'num' vector, then match the cur_column with the 'mtcars_colnamesinside theacross` to return the index and then use that to subset the 'num'
mtcars1 <- mtcars %>%
mutate(across(all_of(mtcars_colnames),
~ num[match(cur_column(), mtcars_colnames)] + .))
Here are 3 base R approaches :
mtcars_colnames <- c("mpg", "cyl", "disp")
num <- c(1, 2, 3)
df <- mtcars
#option 1
df[mtcars_colnames] <- sweep(df[mtcars_colnames], 2, num, `+`)
#option 2
df[mtcars_colnames] <- Map(`+`, df[mtcars_colnames], num)
#option 3
df[mtcars_colnames] <- t(t(df[mtcars_colnames]) + num)
I cannot successfully sum a column in R Studio from a database in SQL. I keep getting the error "Error in FUN: only defined on a data frame with all numeric variables".
Currently, I have:
newObject <- dataFrame %>% sum("COLUMN NAME", na.rm = FALSE)
The problem is that you're trying to pipe the entire dataFrame object into the sum function.
In essence, you're trying this:
newObject <- sum(dataFrame, "COLUMN NAME", na.rm = FALSE)
That isn't working because some of the values in your dataFrame are character. And if they aren't "COLUMN NAME" at the very least is a character string.
You might be looking for summarise, but other possibilities may be transmute or mutate:
mtcars %>%
summarise(Sum = sum(mpg, na.rm= FALSE))
# Sum
#1 642.9
mtcars %>%
transmute(Sum = sum(mpg, na.rm=FALSE))
# Sum
#1 642.9
#2 642.9
#...
mtcars %>%
mutate(Sum = sum(mpg, na.rm= FALSE))
# mpg cyl disp hp drat wt qsec vs am gear carb Sum
#1 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4 642.9
#2 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4 642.9
#...
Here mpg is the name of a column in mtcars. You can replace that with your column name, but without quotes.
Is it possible to use the pipe Operator in R (not to get) but to set data?
Lets say i want to modify the first row of mtcars dataset and set the value of qsec to 99.
Traditional way:
mtcars[1, 7] <- 99
Is that also possible using the pipe Operator?
mtcars %>% filter(qsec == 16.46) %>% select(qsec) <- 99
If we are in a state where the chain is absolute necessary or curious to know whether <- can be applied in a chain
library(magrittr)
mtcars %>%
`[<-`(1, 7, 99) %>%
head(2)
# mpg cyl disp hp drat wt qsec vs am gear carb
#Mazda RX4 21 6 160 110 3.9 2.620 99.00 0 1 4 4
#Mazda RX4 Wag 21 6 160 110 3.9 2.875 17.02 0 1 4 4
Also, inset (from the comments) is an alias for [<-
mtcars %>%
inset(1, 7, 99) %>%
head(2)
So I want to create a new column called var that has the text "testing" for all rows. I.e. the result should be like mtcars$var <- "testing". I have tried different things such as as_name, as_string...
library(tidyverse)
f <- function(df, hello = testing){
df %>%
mutate(var = hello)
}
f(mtcars)
We can do:
f <- function(df, hello = testing){
hello <- deparse(substitute(hello))
df %>%
mutate(var =rlang::as_name(hello))
}
f(mtcars)
However, as pointed out by #Lionel Henry(see comments below):
deparse will not check for simple inputs and might return a character vector. Then as_name() will fail if a length > 1 vector, or do nothing otherwise since it's already a string
as_name(substitute(hello)) does the same but checks the input is a simple symbol or string. It is more constrained than as_label()
It might therefore be better rewritten as:
f <- function(df, hello = testing){
hello <- as_label(substitute(hello))
df %>%
mutate(var = hello )
}
Or:
f <- function(df, hello = testing){
hello <- rlang::as_name(substitute(hello))
df %>%
mutate(var = hello)
}
Result:
mpg cyl disp hp drat wt qsec vs am gear carb var
1 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4 testing
2 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4 testing
3 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1 testing