How to mean columns by values of another column? [duplicate] - r

This question already has answers here:
Aggregate / summarize multiple variables per group (e.g. sum, mean)
(10 answers)
Closed 2 years ago.
I would like to get the mean of a variable according to the group it belongs to.
Here is a reproducible example.
gender <- c("M","F","M","F")
vec1 <- c(1:4)
vec2 <- c(10:13)
df <- data.frame(vec1,vec2,gender)
variables <- names(df)
variables <- variables[-3]
#Wished result
mean1 <- c(mean(c(1,3)),mean(c(2,4)))
mean2 <- c(mean(c(10,12)),mean(c(11,13)))
gender <- c("M","F")
result <- data.frame(gender,mean1,mean2)
How can I achieved such a result ? I would like to use the vector variables, containing the names of the variables to be summarized instead of writing each variables, as my dataset is quite big.

A dplyr solution
library(dplyr)
df %>% group_by(gender) %>% summarise(across(variables, list(mean = mean), .names = "{.fn}_{.col}"))
Output
# A tibble: 2 x 3
gender mean_vec1 mean_vec2
<chr> <dbl> <dbl>
1 F 3 12
2 M 2 11

Use library dplyr
library(dplyr)
gender <- c("M","F","M","F")
df <- data.frame(1:4,gender)
df %>%
group_by(gender) %>%
summarise(mean = X1.4 %>% mean())

Using aggregate.
## formula notation
aggregate(cbind(vec1, vec2) ~ gender, df, FUN=mean)
# gender vec1 vec2
# 1 F 3 12
# 2 M 2 11
## list notation
with(df, aggregate(list(mean=cbind(vec1, vec2)), list(gender=gender), mean))
# gender mean.vec1 mean.vec2
# 1 F 3 12
# 2 M 2 11
If you get an error in the formula notation, it is because you have named another object mean. Use rm(mean) in this case.

Related

Dplyr to calculate mean, SD, and graph multiple variables

I have a table with columns
[Time, var1, var2, var3, var4...varN]
I need to calculate mean/SE per Time for each var1, var2...var n , and I want to do this programmatically for all variables, rather than 1 at a time which would involve a lot of copy-pasting.
Section 8.2.3 here https://tidyeval.tidyverse.org/dplyr.html is close to what I want but my below code:
x <- as.data.frame(matrix(nrow = 2, ncol = 3))
x[1,1] = 1
x[1,2] = 2
x[1,3] = 3
x[2,1] =4
x[2,2] = 5
x[2,3] = 6
names(x)[1] <- "time"
names(x)[2] <- "var1"
names(x)[3] <- "var2"
grouped_mean3 <- function(.data, ...) {
print(.data)
summary_vars <- enquos(...)
print(summary_vars)
summary_vars <- purrr::map(summary_vars, function(var) {
expr(mean(!!var, na.rm = TRUE))
})
print(summary_vars)
.data %>%
group_by(time)
summarise(!!!summary_vars) # Unquote-splice the list
}
grouped_mean3(x, var("var1"), var("var2"))
Yields
Error in !summary_vars : invalid argument type
And the original cause is "Must group by variables found in .data." and it finds a column that isn't in the dummy "x" that I generated for the purposes of testing. I have no idea what's happening, sadly.
How do I actually extract the mean from the new summary_vars and add it to the .data table? summary_vars becomes something like
[[1]]
mean(~var1, na.rm = TRUE)
[[2]]
mean(~var2, na.rm = TRUE)
Which seems close, but needs evaluation. How do I evaluate this? !!! wasn't working.
For what it's worth, I tried plugging the example in dplyr into this R engine https://rdrr.io/cran/dplyr/man/starwars.html and it didn't work either.
Help?
End goal would be a table along the lines of
[Time, var1mean, var2mean, var3mean, var4mean...]
Try this :
library(dplyr)
grouped_mean3 <- function(.data, ...) {
vars <- c(...)
.data %>%
group_by(time) %>%
summarise(across(all_of(vars), mean))
}
grouped_mean3(x, 'var1')
# time var1mean
# <dbl> <dbl>
#1 1 2
#2 4 5
grouped_mean3(x, 'var1', 'var2')
# time var1mean var2mean
# <dbl> <dbl> <dbl>
#1 1 2 3
#2 4 5 6
Perhaps this is what you are looking for?
x %>%
group_by(time) %>%
summarise_at(vars(starts_with('var')), ~mean(.,na.rm=T)) %>%
rename_at(vars(starts_with('var')),funs(paste(.,"mean"))) %>%
merge(x)
With your data (from your question) following is the output:
time var1mean var2mean var1 var2
1 1 2 3 2 3
2 4 5 6 5 6

Problem with sample_n(2, replace=F) in a list of data with some data's size less than 2

I need help with sample_n() in ‘dplyr’ in R:
I have a list of data riskset[[1]], riskset[[2]],..., riskset[[1000]]), each element riskset[[i]] of the list is a data frame of observations, and I divided the observations in each riskset into group 1:4 based on the distribution of a variable. So the data in riskset[[i]] looks like this:
id sex grp ...
1 F 1 ...
2 M 3 ...
3 F 1 ...
4 M 4 ...
5 F 2 ...
6 F 3 ...
......................
I want to sample 2 observations from each grp within each riskset and save them as a list of sample. I used
sample<- list()
for(i in 1:1000){
sample[[i]] <- riskset[[i]] %>% group_by(grp) %>% sample_n(2,replace=F)
}
It gave me error:
size must be less or equal than 1 (size of data), set ‘replace = TRUE’ to use sampling with replacement.
I tried the code on the riskset which has more than 2 obs in each grp, it worked. But it doesn’t work on the riskset which has less than 2 obs in some group. For the group that has less than 2 obs, I want all the obs it has. And for the group that has more than 2 obs, I want to sample 2 obs without replacement. How can I achieve my sampling goal using R functions? Thanks in advance!
We can use map to loop over the list ('riskset'), then grouped by 'grp', apply the sample_n
library(tidyerse)
out <- map(riskset, ~ .x %>%
group_by(grp) %>%
sample_n(pmin(n(), 2), replace = TRUE))
Or another option is slice
map(riskset, ~ .x %>%
group_by(grp) %>%
slice(if(n() < 2) 1 else sample(row_number(), 2))
Or without using if/else
map(riskset, ~ .x %>%
group_by(grp) %>%
slice(sample(seq_len(pmin(n(), 2)))))
data
iris1 <- iris %>%
select(grp = Species, everything()) %>%
slice(c(1:5, 51))
riskset <- list(iris1, iris1)

How to group multiple rows based on some criteria and sum values in R?

Hi All,
Example :- The above is the data I have. I want to group age 1-2 and count the values. In this data value is 4 for age group 1-2. Similarly I want to group age 3-4 and count the values. Here the value for age group 3-4 is 6.
How can I group age and aggregate the values correspond to it?
I know this way: code-
data.frame(df %>% group_by(df$Age) %>% tally())
But the values are aggregating on individual Age.
I want the values aggregating on multiple age to be a group as mentioned above example.
Any help on this will be greatly helpful.
Thanks a lot to All.
Here are two solutions, with base R and with package dplyr.
I will use the data posted by Shree.
First, base R.
I create a grouping variable grp and then aggregate on it.
grp <- with(df, c((age %in% 1:2) + 2*(age %in% 3:4)))
aggregate(age ~ grp, df, length)
# grp age
#1 1 4
#2 2 6
Second a dplyr way.
Function case_when is used to create a grouping variable. This allows for meaningful names to be given to the groups in an easy way.
library(dplyr)
df %>%
mutate(grp = case_when(
age %in% 1:2 ~ "2:3",
age %in% 3:4 ~ "3:4",
TRUE ~ NA_character_
)) %>%
group_by(grp) %>%
tally()
## A tibble: 2 x 2
# grp n
# <chr> <int>
#1 1:2 4
#2 3:4 6
Here's one way using dplyr and ?cut from base R -
df <- data.frame(age = c(1,1,2,2,3,3,3,4,4,4),
Name = letters[1:10],
stringsAsFactors = F)
df %>%
count(grp = cut(age, breaks = c(0,2,4)))
# A tibble: 2 x 2
grp n
<fct> <int>
1 (0,2] 4
2 (2,4] 6

Compute variable according to factor levels

I am kind of new to R and programming in general. I am currently strugling with a piece of code for data transformation and hope someone can take a little bit of time to help me.
Below a reproducible exemple :
# Data
a <- c(rnorm(12, 20))
b <- c(rnorm(12, 25))
f1 <- rep(c("X","Y","Z"), each=4) #family
f2 <- rep(x = c(0,1,50,100), 3) #reference and test levels
dt <- data.frame(f1=factor(f1), f2=factor(f2), a,b)
#library loading
library(tidyverse)
Goal : Compute all values (a,b) using a reference value. Calculation should be : a/a_ref with a_ref = a when f2=0 depending on the family (f1 can be X,Y or Z).
I tried to solve this by using this code :
test <- filter(dt, f2!=0) %>% group_by(f1) %>%
mutate("a/a_ref"=a/(filter(dt, f2==0) %>% group_by(f1) %>% distinct(a) %>% pull))
I get :
test results
as you can see a is divided by a_ref. But my script seems to recycle the use of reference values (a_ref) regardless of the family f1.
Do you have any suggestion so A is computed with regard of the family (f1) ?
Thank you for reading !
EDIT
I found a way to do it 'manualy'
filter(dt, f1=="X") %>% mutate("a/a_ref"=a/(filter(dt, f1=="X" & f2==0) %>% distinct(a) %>% pull()))
f1 f2 a b a/a_ref
1 X 0 21.77605 24.53115 1.0000000
2 X 1 20.17327 24.02512 0.9263973
3 X 50 19.81482 25.58103 0.9099366
4 X 100 19.90205 24.66322 0.9139422
the problem is that I'd have to update the code for each variable and family and thus is not a clean way to do it.
# use this to reproduce the same dataset and results
set.seed(5)
# Data
a <- c(rnorm(12, 20))
b <- c(rnorm(12, 25))
f1 <- rep(c("X","Y","Z"), each=4) #family
f2 <- rep(x = c(0,1,50,100), 3) #reference and test levels
dt <- data.frame(f1=factor(f1), f2=factor(f2), a,b)
#library loading
library(tidyverse)
dt %>%
group_by(f1) %>% # for each f1 value
mutate(a_ref = a[f2 == 0], # get the a_ref and add it in each row
"a/a_ref" = a/a_ref) %>% # divide a and a_ref
ungroup() %>% # forget the grouping
filter(f2 != 0) # remove rows where f2 == 0
# # A tibble: 9 x 6
# f1 f2 a b a_ref `a/a_ref`
# <fctr> <fctr> <dbl> <dbl> <dbl> <dbl>
# 1 X 1 21.38436 24.84247 19.15914 1.1161437
# 2 X 50 18.74451 23.92824 19.15914 0.9783583
# 3 X 100 20.07014 24.86101 19.15914 1.0475490
# 4 Y 1 19.39709 22.81603 21.71144 0.8934042
# 5 Y 50 19.52783 25.24082 21.71144 0.8994260
# 6 Y 100 19.36463 24.74064 21.71144 0.8919090
# 7 Z 1 20.13811 25.94187 19.71423 1.0215013
# 8 Z 50 21.22763 26.46796 19.71423 1.0767671
# 9 Z 100 19.19822 25.70676 19.71423 0.9738257
You can do this for more than one variable using:
dt %>%
group_by(f1) %>%
mutate_at(vars(a:b), funs(./.[f2 == 0])) %>%
ungroup()
Or generally use vars(a:z) to use all variables between a and z as long as they are one after the other in your dataset.
Another solution could be using mutate_if like:
dt %>%
group_by(f1) %>%
mutate_if(is.numeric, funs(./.[f2 == 0])) %>%
ungroup()
Where the function will be applied to all numeric variables you have. The variables f1 and f2 will be factor variables, so it just excludes those ones.

Using dplyr::select semantics within a dplyr::mutate function [duplicate]

This question already has answers here:
Performing dplyr mutate on subset of columns
(5 answers)
Closed 5 years ago.
What I'm trying to do here is bring in dplyr::select() semantics into a function supplied to dplyr::mutate(). Below is a minimal example.
dat <- tibble(class = rep(c("A", "B"), each = 10),
x = sample(100, 20),
y = sample(100, 20),
z = sample(100, 20))
.reorder_rows <- function(...) {
x <- list(...)
y <- as.matrix(do.call("cbind", x))
h <- hclust(dist(y))
return(h$order)
}
dat %>%
group_by(class) %>%
mutate(h_order = .reorder_rows(x, y, z))
## class x y z h_order
## <chr> <int> <int> <int> <int>
## 1 A 85 17 5 1
## 2 A 67 24 35 5
## ...
## 18 B 76 7 94 9
## 19 B 65 39 85 8
## 20 B 49 11 100 10
##
## Note: function applied across each group, A and B
What I would like to do is something along the lines of:
dat %>%
group_by(class) %>%
mutate(h_order = .reorder_rows(-class))
The reason this is important is that when dat has many more variables, I need to be able to exclude the grouping/specific variables from the function's calculation.
I'm not sure how this would be implemented, but somehow using select semantics within the .reorder_rows function might be one way to tackle this problem.
For this particular approach, you should probably nest and unnest (using tidyr) by class rather than grouping by it:
library(tidyr)
library(purrr)
dat %>%
nest(-class) %>%
mutate(h_order = map(data, .reorder_rows)) %>%
unnest()
Incidentally, notice that while this works with your function you could also write a shorter version that takes the data frame directly:
.reorder_rows <- function(x) {
h <- hclust(dist(as.matrix(x)))
return(h$order)
}

Resources