Question:
How do I modify an element within an established list and re-assign it back to the list in the same index/position?
Setup and Example
First, here's a dataframe that I am breaking up by groups into a list:
library(tidyverse) # Not absolutely required, but I'm working this way.
df <- tibble(A = rep(paste("Group", c(1:3)),3),
B = seq(1, 18, 2),
C = (1:9))
lst <- df %>%
group_by(A) %>%
group_split()
Dataframe and resulting list should be as below:
> df
# A tibble: 9 × 3
A B C
<chr> <dbl> <int>
1 Group 1 1 1
2 Group 2 3 2
3 Group 3 5 3
4 Group 1 7 4
5 Group 2 9 5
6 Group 3 11 6
7 Group 1 13 7
8 Group 2 15 8
9 Group 3 17 9
> lst
<list_of<
tbl_df<
A: character
B: double
C: integer
>
>[3]>
[[1]]
# A tibble: 3 × 3
A B C
<chr> <dbl> <int>
1 Group 1 1 1
2 Group 1 7 4
3 Group 1 13 7
[[2]]
# A tibble: 3 × 3
A B C
<chr> <dbl> <int>
1 Group 2 3 2
2 Group 2 9 5
3 Group 2 15 8
[[3]]
# A tibble: 3 × 3
A B C
<chr> <dbl> <int>
1 Group 3 5 3
2 Group 3 11 6
3 Group 3 17 9
Here's the problem...
For reasons irrelevant here, I need to give each sub-dataframe in the list a different treatment based on the Group. I thought I could apply the modifications in a loop like below, leaving the list structure in place.
for (j in 1:3){
lst[[j]] <- lst[[j]] %>%
mutate(D = B * C)
}
...but that throws this error:
Error in `[[<-`:
! Can't convert from `value` <tbl_df<
A: character
B: double
C: integer
D: double
>> to <tbl_df<
A: character
B: double
C: integer
>> due to loss of precision.
I know that the assignment back to the list is the problem, because I can successfully do this:
df2 <- NULL
df_final <- NULL
for (j in 1:3){
df2 <- lst[[j]] %>%
mutate(D = B * C)
df_final <- rbind(df_final, df2)
}
df_final
...which returns a dataframe that I could break back up as in the beginning.
> df_final
# A tibble: 9 × 4
A B C D
<chr> <dbl> <int> <dbl>
1 Group 1 1 1 1
2 Group 1 7 4 28
3 Group 1 13 7 91
4 Group 2 3 2 6
5 Group 2 9 5 45
6 Group 2 15 8 120
7 Group 3 5 3 15
8 Group 3 11 6 66
9 Group 3 17 9 153
...but I feel like I'm missing some nuance in how the list could be assigned "in place" as above, and I don't understand the error message. What am I missing about assigning to lists that makes lst[[j]] <- lst[[j]] %>% <ANY MODIFICATION>
fail?
CodePudding user response:
The result of group_split()
is not a simple list, but has some track to the tables inside it that prevents modification of only one item.
You can avoid this with lst <- as.list(lst)
.
library(dplyr) # Not absolutely required, but I'm working this way.
df <- tibble(A = rep(paste("Group", c(1:3)),3),
B = seq(1, 18, 2),
C = (1:9))
lst <- df %>%
group_by(A) %>%
group_split()
for (j in 1:3){
lst[[j]] <- lst[[j]] %>%
mutate(D = B * C)
}
#> Error in `[[<-`:
#> ! Can't convert from `value` <tbl_df<
#> A: character
#> B: double
#> C: integer
#> D: double
#> >> to <tbl_df<
#> A: character
#> B: double
#> C: integer
#> >> due to loss of precision.
lst <- as.list(lst)
for (j in 1:3){
lst[[j]] <- lst[[j]] %>%
mutate(D = B * C)
} # OK
df_final <- bind_rows(lst)
df_final
#> # A tibble: 9 × 4
#> A B C D
#> <chr> <dbl> <int> <dbl>
#> 1 Group 1 1 1 1
#> 2 Group 1 7 4 28
#> 3 Group 1 13 7 91
#> 4 Group 2 3 2 6
#> 5 Group 2 9 5 45
#> 6 Group 2 15 8 120
#> 7 Group 3 5 3 15
#> 8 Group 3 11 6 66
#> 9 Group 3 17 9 153
OR
you can use map
to map the function to every item of the list.
lst <- map(lst, ~ mutate(., D=B*C))
df_final <- bind_rows(lst)
df_final
CodePudding user response:
Here's a potential solution using purrr
:
library(tidyverse) # Not absolutely required, but I'm working this way.
df <- tibble(A = rep(paste("Group", c(1:3)),3),
B = seq(1, 18, 2),
C = (1:9))
df %>%
group_by(A) %>%
group_split() %>%
map_df(~.x %>% mutate(D = B * C))
#> # A tibble: 9 × 4
#> A B C D
#> <chr> <dbl> <int> <dbl>
#> 1 Group 1 1 1 1
#> 2 Group 1 7 4 28
#> 3 Group 1 13 7 91
#> 4 Group 2 3 2 6
#> 5 Group 2 9 5 45
#> 6 Group 2 15 8 120
#> 7 Group 3 5 3 15
#> 8 Group 3 11 6 66
#> 9 Group 3 17 9 153
# conditionally mutate based on group
# could also just do this with a dataframe though
df %>%
split(.$A) %>%
map_df(function(x) {
x %>%
mutate(D = case_when(A == "Group 1" ~ B C,
A == "Group 2" ~ B 1000,
A == "Group 3" ~ B * C))
})
#> # A tibble: 9 × 4
#> A B C D
#> <chr> <dbl> <int> <dbl>
#> 1 Group 1 1 1 2
#> 2 Group 1 7 4 11
#> 3 Group 1 13 7 20
#> 4 Group 2 3 2 1003
#> 5 Group 2 9 5 1009
#> 6 Group 2 15 8 1015
#> 7 Group 3 5 3 15
#> 8 Group 3 11 6 66
#> 9 Group 3 17 9 153
Created on 2022-10-19 by the reprex package (v2.0.1)
CodePudding user response:
Using R base functions also works
tmp <- lapply(lst, function(x){
do.call(cbind, list(x, D=(x$B*x$C)))
})
do.call(rbind, tmp)
Result:
A B C D
1 Group 1 1 1 1
2 Group 1 7 4 28
3 Group 1 13 7 91
4 Group 2 3 2 6
5 Group 2 9 5 45
6 Group 2 15 8 120
7 Group 3 5 3 15
8 Group 3 11 6 66
9 Group 3 17 9 153