group by two sets of vars in a function - r

I'm using the sample dataset below:
mytable <- read.table(text=
"group team num ID
1 a x 1 9
2 a x 2 4
3 a y 3 5
4 a y 4 9
5 b x 1 7
6 b y 4 4
7 b x 3 9
8 b y 2 8",
header = TRUE, stringsAsFactors = FALSE)
I want to create separate data frames for each set of variables that I want to group by, I also want to group by two variables as well... I'm not sure how to do that. For example, I want a separate dataframe that groups the data by both team and ID as well... how do I do that?
library(dplyr)
lapply(c("group","team","ID",c("team","ID")), function(x){
group_by(mytable,across(c(x,num)))%>%summarise(Count = n()) %>% mutate(new=x)%>% as.data.frame()
})

See if this is what you want.
library(dplyr)
cols <- list("group","team","ID", c("team","ID"))
lapply(cols, function(x, dat = mytable){
dat2 <- dat %>%
group_by(across({{x}})) %>%
summarise(Count = n()) %>%
mutate(new = toString(x)) %>%
as.data.frame()
return(dat2)
})
# `summarise()` has grouped output by 'team'. You can override using the `.groups` argument.
# [[1]]
# group Count new
# 1 a 4 group
# 2 b 4 group
#
# [[2]]
# team Count new
# 1 x 4 team
# 2 y 4 team
#
# [[3]]
# ID Count new
# 1 4 2 ID
# 2 5 1 ID
# 3 7 1 ID
# 4 8 1 ID
# 5 9 3 ID
#
# [[4]]
# team ID Count new
# 1 x 4 1 team, ID
# 2 x 7 1 team, ID
# 3 x 9 2 team, ID
# 4 y 4 1 team, ID
# 5 y 5 1 team, ID
# 6 y 8 1 team, ID
# 7 y 9 1 team, ID

Does this, based on tidyverse, give you what you want?
library(tidyverse)
ytable %>%
group_by(team, ID) %>%
group_split()
<list_of<
tbl_df<
group: character
team : character
num : integer
ID : integer
>
>[7]>
[[1]]
# A tibble: 1 × 4
group team num ID
<chr> <chr> <int> <int>
1 a x 2 4
[[2]]
# A tibble: 1 × 4
group team num ID
<chr> <chr> <int> <int>
1 b x 1 7
[[3]]
# A tibble: 2 × 4
group team num ID
<chr> <chr> <int> <int>
1 a x 1 9
2 b x 3 9
[[4]]
# A tibble: 1 × 4
group team num ID
<chr> <chr> <int> <int>
1 b y 4 4
[[5]]
# A tibble: 1 × 4
group team num ID
<chr> <chr> <int> <int>
1 a y 3 5
[[6]]
# A tibble: 1 × 4
group team num ID
<chr> <chr> <int> <int>
1 b y 2 8
[[7]]
# A tibble: 1 × 4
group team num ID
<chr> <chr> <int> <int>
1 a y 4 9

Related

How to keep ties when using dplyr distinct function?

I'm using dplyr distinct() with multiple variables and am trying to figure out how to handle "ties". For example, when running the code at the bottom of this post against example data frame label_1, I'd like to get these results in situations like this where there's a tie with eleCnt and grpID variables:
Element Group eleCnt grpID grpRnk Explain grpRnk column...
<chr> <dbl> <int> <int> <int>
1 R 1 1 3 1 Ranked 1st since it has lowest eleCnt & lowest grpID
2 X 3 1 3 1 Also ranked 1st since it ties with above in terms of eleCnt and grpID
3 R 2 3 7 2 Ranked 2nd since its eleCnt is 2nd and its grpRnk is 2nd
When I run the code against data frame label_2, there are no ties and the code gives me this correct output:
Element Group eleCnt grpID grpRnk
<chr> <dbl> <dbl> <dbl> <int>
1 B 2 1 3 1
2 R 3 1 6 2
3 X 4 1 10 3
4 R 1 4 9 4
5 R 2 6 13 5
Any recommendations for an efficient way to do this, preferably in dplyr? Maybe distinct() isn't the right function to be using?
Code:
library(dplyr)
label_1 <- data.frame(Element=c("B","R","R","R","R","B","X","X","X","X","X"),
Group = c(0,1,1,2,2,0,3,3,0,0,0),
eleCnt = c(1,1,2,3,4,2,1,2,3,4,5),
grpID = c(0,3,3,7,7,0,3,3,0,0,0))
label_2 <- data.frame(Element = c("R","R","R","X","X","X","X","B","B","R","R","R","R"),
Group = c(3,3,3,4,4,4,4,2,2,1,1,2,2),
eleCnt = c(1,2,3,1,2,3,4,1,2,4,5,6,7),
grpID = c(6,6,6,10,10,10,10,3,3,9,9,13,13))
label_2 %>% select(Element,Group,eleCnt,grpID) %>%
filter(Group > 0) %>%
group_by(Element,Group) %>%
slice(which.min(Group)) %>%
ungroup() %>%
distinct(eleCnt,grpID, .keep_all = TRUE) %>%
arrange(eleCnt,grpID) %>%
mutate(grpRnk = 1:n())
Perhaps you can leverage data.table::rleid() function, like this:
f <- function(lab) {
filter(lab,Group!=0) %>%
arrange(eleCnt,grpID) %>%
mutate(grpRnk = data.table::rleid(eleCnt,grpID)) %>%
group_by(grpID) %>%
filter(grpRnk==min(grpRnk))
}
Apply f() to label_1
f(label_1)
# A tibble: 3 x 5
# Groups: grpID [2]
Element Group eleCnt grpID grpRnk
<chr> <dbl> <dbl> <dbl> <int>
1 R 1 1 3 1
2 X 3 1 3 1
3 R 2 3 7 3
Apply f() to label_1
f(label_2)
# A tibble: 5 x 5
# Groups: grpID [5]
Element Group eleCnt grpID grpRnk
<chr> <dbl> <dbl> <dbl> <int>
1 B 2 1 3 1
2 R 3 1 6 2
3 X 4 1 10 3
4 R 1 4 9 9
5 R 2 6 13 12

How to use dplyr distinct function with multiple data frame variables and when there are ties?

I'm using dplyr distinct() for the first time and I'm trying to figure out how to use it with multiple variables and how to handle "ties". For example, when I run the code shown at the bottom of this post against example data frame label_18, I get the below correct results as shown and explained here (note that there no ties with eleCnt and grpID columns in this example):
Element Group eleCnt grpID grpRnk Explain grpRnk column...
<chr> <dbl> <int> <int> <int>
1 B 2 1 3 1 Ranked 1st since it has lowest eleCnt & lowest grpID
2 R 3 1 6 2 Ranked 2nd since it has lowest elecCnt & 2nd lowest grpID
3 X 4 1 10 3 Same pattern as above
4 R 1 4 9 4 Same pattern as above
5 R 2 6 13 5 Same pattern as above
Now when I run the code against label_7, there is a tie between eleCnt and grpID, and I get these results:
Element Group eleCnt grpID grpRnk
<chr> <dbl> <int> <int> <int>
1 R 1 1 3 1
2 R 2 3 7 2
Expected output: I would like the results for label_7 to be (while retaining the output for label_18 shown above):
Element Group eleCnt grpID grpRnk Explain grpRnk column...
<chr> <dbl> <int> <int> <int>
1 R 1 1 3 1 Ranked 1st since it has lowest eleCnt & lowest grpID
2 X 3 1 3 1 Also ranked 1st since it ties with above
3 R 2 3 7 2 Ranked 2nd since its eleCnt is 2nd and its grpRnk is 2nd
How do I modify distinct() for handling ties, so I can get the desired results for label_7 while keeping the same results for label_18? Maybe there's a better way to do this completely, some function other than distinct() for this sort of thing.
Code:
library(dplyr)
label_7 <- data.frame(Element=c("B","R","R","R","R","B","X","X","X","X","X"),
Group = c(0,1,1,2,2,0,3,3,0,0,0),
eleCnt = c(1,1,2,3,4,2,1,2,3,4,5),
grpID = c(0,3,3,7,7,0,3,3,0,0,0))
label_18 <- data.frame(Element = c("R","R","R","X","X","X","X","B","B","R","R","R","R"),
Group = c(3,3,3,4,4,4,4,2,2,1,1,2,2),
eleCnt = c(1,2,3,1,2,3,4,1,2,4,5,6,7),
grpID = c(6,6,6,10,10,10,10,3,3,9,9,13,13))
label_7 %>% select(Element,Group,eleCnt,grpID) %>%
filter(Group > 0) %>%
group_by(Element,Group) %>%
slice(which.min(Group)) %>%
ungroup() %>%
distinct(eleCnt,grpID, .keep_all = TRUE) %>%
arrange(eleCnt,grpID) %>%
mutate(grpRnk = 1:n())
Edit: adding another data frame to test against, label_15 --
> label_15
Element Group eleCnt grpID
1 B 0 1 0
2 R 1 1 3
3 R 1 2 3
4 R 0 3 0
5 X 2 1 3
6 X 2 2 3
7 X 3 3 7
8 X 3 4 7
Expected results would be similar to label_7, because of a tie between Elements R and X in rows 2 and 5 of the above data frame:
Element Group eleCnt grpID grpRank
<chr> <dbl> <dbl> <dbl> <int>
1 R 1 1 3 1
2 X 2 1 3 1
3 X 3 3 7 2
Code for label_15 data frame:
label_15 <- data.frame(Element = c("B","R","R","R","X","X","X","X"),
Group = c(0,1,1,0,2,2,3,3),
eleCnt = c(1,1,2,3,1,2,3,4),
grpID = c(0,3,3,0,3,3,7,7))
We could try
library(dplyr)
library(data.table)
label_7 %>%
select(Element,Group,eleCnt,grpID) %>%
filter(Group > 0) %>%
group_by(Element,Group) %>%
slice(which.min(Group)) %>%
ungroup() %>%
distinct(tmp = rleid(eleCnt, grpID), .keep_all = TRUE) %>%
arrange(eleCnt,grpID) %>%
select(-tmp) %>%
mutate(grpRank= match(grpID, unique(grpID)))
-output
# A tibble: 3 × 5
Element Group eleCnt grpID grpRank
<chr> <dbl> <dbl> <dbl> <int>
1 R 1 1 3 1
2 X 3 1 3 1
3 R 2 3 7 2
For the second case
label_18 %>%
select(Element,Group,eleCnt,grpID) %>%
filter(Group > 0) %>%
group_by(Element,Group) %>%
slice(which.min(Group)) %>%
ungroup() %>%
distinct(tmp = rleid(eleCnt, grpID), .keep_all = TRUE) %>%
arrange(eleCnt,grpID) %>%
select(-tmp) %>%
mutate(grpRank= match(grpID, unique(grpID)))
-output
# A tibble: 5 × 5
Element Group eleCnt grpID grpRank
<chr> <dbl> <dbl> <dbl> <int>
1 B 2 1 3 1
2 R 3 1 6 2
3 X 4 1 10 3
4 R 1 4 9 4
5 R 2 6 13 5
Here's another possibility that correctly processes all 3 scenarios in the post:
filter(label_15,Group!=0) %>%
arrange(eleCnt,grpID) %>%
mutate(grpRnk = data.table::rleid(eleCnt,grpID)) %>%
group_by(grpID) %>%
filter(grpRnk==min(grpRnk)) %>%
ungroup %>%
mutate(grpRnk=data.table::rleid(grpID))
Output:
# A tibble: 3 x 5
Element Group eleCnt grpID grpRnk
<chr> <dbl> <dbl> <dbl> <int>
1 R 1 1 3 1
2 X 2 1 3 1
3 X 3 3 7 2

How to pivot_wider while preserving duplicate column values?

looking for some help on this maybe basic issue. Suppose I have the following:
tibble(
x= c("a","a","b","b","b","b"),
y= c(1,2,1,2,1,2)
)
# A tibble: 6 x 2
# x y
# <chr> <dbl>
# 1 a 1
# 2 a 2
# 3 b 1
# 4 b 2
# 5 b 1
# 6 b 2
I would like to transform to the following tibble:
tibble(
x= c("a","b","b"),
y.1= c("1","1","1"),
y.2= c("2","2","2")
)
# A tibble: 3 x 3
# x y.1 y.2
# <chr> <chr> <chr>
# 1 a 1 2
# 2 b 1 2
# 3 b 1 2
What's the best way to achieve this? I tried to use tidyr::pivot_wider but I couldn't figure it out without preserving the x column's two "b" values.
Given the current structure, one possible approach is to use vector recycling
library(tidyverse)
df = tibble(
x= c("a","a","b","b","b","b"),
y= c(1,2,1,2,1,2)
)
df %>%
summarise(
x = x[c(T, F)],
y.1 = y[c(T, F)],
y.2 = y[c(F, T)]
)
#> # A tibble: 3 x 3
#> x y.1 y.2
#> <chr> <dbl> <dbl>
#> 1 a 1 2
#> 2 b 1 2
#> 3 b 1 2
Although this could break if data is not in the proper sequence.

Filter a tibble with two conditions

i have this tibble..
tibble(id=c(4,4), client=c(5,10), stock=c(NA,10))
# A tibble: 2 x 3
id client stock
<dbl> <dbl> <dbl>
1 4 5 NA
2 4 10 10
from which i want to keep the row where client == 5 and stock == 10. How would i filter that? So my desired outcome would be:
# A tibble: 1 x 3
a client stock
<dbl> <dbl> <dbl>
1 4 5 10
Not sure about the context of filtering using values from different rows, but see if below operation works for you.
> library(dplyr)
> df %>% fill(stock, .direction = 'up') %>% filter(client == 5 & stock == 10)
# A tibble: 1 x 3
id client stock
<dbl> <dbl> <dbl>
1 4 5 10

How to mutate a variable in a tibble based on columns named as numbers in R

I have a tibble with columns named as numbers (e.g. 1). I created a function to compute differences between columns, but I don't know how to do it with that type of columns:
<!-- language-all: lang-r -->
library(tidyverse)
df <- tibble(`1` = c(1,2,3), `2` = c(2,4,6))
# This works
df %>%
mutate(diff = `1` - `2`)
#> # A tibble: 3 x 3
#> `1` `2` diff
#> <dbl> <dbl> <dbl>
#> 1 1 2 -1
#> 2 2 4 -2
#> 3 3 6 -3
# But this doesn't
calc_diffs <- function(x, y){
df %>%
mutate(diff := !!x - !!y)
}
calc_diffs(1, 2)
#> # A tibble: 3 x 3
#> `1` `2` diff
#> <dbl> <dbl> <dbl>
#> 1 1 2 -1
#> 2 2 4 -1
#> 3 3 6 -1
<sup>Created on 2020-10-14 by the [reprex package](https://reprex.tidyverse.org) (v0.3.0)</sup>
We can convert to a symbol and evaluate
calc_diffs <- function(x, y){
df %>%
mutate(diff := !! rlang::sym(x) - !!rlang::sym(y))
}
Then, we just pass a string as argument
calc_diffs("1", "2")
# A tibble: 3 x 3
# `1` `2` diff
# <dbl> <dbl> <dbl>
#1 1 2 -1
#2 2 4 -2
#3 3 6 -3
Column names are strings. We could pass index to subset the column, but here the column name is an unusual name that starts with number. So, either we can wrap it with backreference using paste or just pass a string, convert to symbol and evaluate (!!)
Does this work:
> df <- tibble(`1` = c(1,2,3), `2` = c(2,4,6))
> df
# A tibble: 3 x 2
`1` `2`
<dbl> <dbl>
1 1 2
2 2 4
3 3 6
> calc_diffs <- function(x, y){
+ df %>%
+ mutate(diff = {{x}} - {{y}})
+ }
> calc_diffs(`1`,`2`)
# A tibble: 3 x 3
`1` `2` diff
<dbl> <dbl> <dbl>
1 1 2 -1
2 2 4 -2
3 3 6 -3
>

Resources