Krutik
Krutik

Reputation: 501

How to loop through a list of lists, whilst subsetting the lists based on a variable

I have a list of lists similar to the toy example given here. I would like to loop through this list to return a new list which has had elements removed based on a variable.


dput(head(list)):

list(FEB_gems = list(GAME1 = structure(list(GAME1_Class = structure(c(2L, 
1L, 5L, 4L, 3L), .Label = c("fighter", "paladin", "rouge", "sorcerer", 
"wizard"), class = "factor"), GAME1_Race = structure(c(3L, 1L, 
4L, 3L, 2L), .Label = c("elf", "gnome", "human", "orc"), class = "factor"), 
GAME1_Alignment = structure(c(4L, 2L, 1L, 5L, 3L), .Label = c("CE", 
"CG", "LG", "NE", "NN"), class = "factor"), GAME1_Level = c(6, 
7, 6, 7, 7), GAME1_Alive = structure(c(1L, 1L, 1L, 1L, 1L
), .Label = "y", class = "factor")), class = "data.frame", row.names = c(NA, 
-5L)), GAME2 = structure(list(GAME2_Class = structure(c(3L, 5L, 
2L, 4L, 1L), .Label = c("bard", "cleric", "fighter", "monk", 
"wizard"), class = "factor"), GAME2_Race = structure(c(2L, 3L, 
2L, 4L, 1L), .Label = c("dwarf", "elf", "half-elf", "human"), class = "factor"), 
GAME2_Alignment = structure(c(4L, 2L, 1L, 5L, 3L), .Label = c("CE", 
"CG", "LG", "NE", "NN"), class = "factor"), GAME2_Level = c(5, 
5, 5, 5, 5), GAME2_Alive = structure(c(1L, 2L, 2L, 2L, 2L
), .Label = c("n", "y"), class = "factor")), class = "data.frame", row.names = c(NA, 
-5L))), MAR_gems = list(GAME3 = structure(list(GAME3_Class = structure(c(2L, 
1L, 5L, 4L, 3L), .Label = c("barbarian", "cleric", "monk", "ranger", 
"warlock"), class = "factor"), GAME3_Race = structure(c(2L, 3L, 
2L, 4L, 1L), .Label = c("dwarf", "elf", "half-elf", "human"), class = "factor"), 
GAME3_Alignment = structure(c(2L, 2L, 1L, 3L, 2L), .Label = c("CE", 
"LG", "LN"), class = "factor"), GAME3_Level = c(1, 1, 1, 
1, 1), GAME3_Alive = structure(c(2L, 2L, 2L, 1L, 2L), .Label = c("n", 
"y"), class = "factor")), class = "data.frame", row.names = c(NA, 
-5L)), GAME4 = structure(list(GAME4_Class = structure(c(2L, 1L, 
5L, 4L, 3L), .Label = c("fighter", "paladin", "rouge", "sorcerer", 
"wizard"), class = "factor"), GAME4_Race = structure(c(2L, 3L, 
 2L, 4L, 1L), .Label = c("dwarf", "elf", "half-elf", "human"), class = "factor"), 
GAME4_Alignment = structure(c(1L, 2L, 1L, 4L, 3L), .Label = c("CE", 
"CG", "LG", "LN"), class = "factor"), GAME4_Level = c(5, 
5, 5, 5, 5), GAME4_Alive = structure(c(1L, 2L, 2L, 2L, 2L
), .Label = c("n", "y"), class = "factor")), class = "data.frame", row.names = c(NA, 
-5L))))

I have made some attempt at manually sub-setting the list of lists. A function would be preferable is because I have multiple types of data to subset.

1) sub-setting Level columns based on interger

df1 <- Games.Split[[1]][[1]]
Level <- df1[which(df1[4] > 6),]
Games.Split[[1]][[1]] <- Level

df1:

  GAME1_Class GAME1_Race GAME1_Alignment GAME1_Level GAME1_Alive
1     paladin      human              NE           6           y
2     fighter        elf              CG           7           y
3      wizard        orc              CE           6           y
4    sorcerer      human              NN           7           y
5       rouge      gnome              LG           7           y

Level:

 GAME1_Class GAME1_Race GAME1_Alignment GAME1_Level GAME1_Alive
2     fighter        elf              CG           7           y
4    sorcerer      human              NN           7           y
5       rouge      gnome              LG           7           y

2) sub-setting Alive columns based on string

df2 <- Games.Split[[1]][[2]]
Alive <- df2[which(df2[5] == 'y'),]
Games.Split[[1]][[2]] <- Alive

df2:

 GAME2_Class GAME2_Race GAME2_Alignment GAME2_Level GAME2_Alive
1     fighter        elf              NE           5           n
2      wizard   half-elf              CG           5           y
3      cleric        elf              CE           5           y
4        monk      human              NN           5           y
5        bard      dwarf              LG           5           y

Alive:

 GAME2_Class GAME2_Race GAME2_Alignment GAME2_Level GAME2_Alive
2      wizard   half-elf              CG           5           y
3      cleric        elf              CE           5           y
4        monk      human              NN           5           y
5        bard      dwarf              LG           5           y

However I'm struggling to put this into action in a for loop to perform these sub-setting tasks on the entire list.

for (i in Games.Split){
  for (j in i){
    Alive = j[which(j[5] == 'y'),]
    j <- Alive
  }
}

Overall, a function/ method that can iterate through the whole list to provide a new list which no longer has the removed elements.

Upvotes: 1

Views: 117

Answers (3)

Andrew Royal
Andrew Royal

Reputation: 346

Since you have two levels of lists to organizing the dataframes, this will require a nested list apply function (lapply)-- same as a loop but a little neater. Here is an example that creates a function to subset the game list (gameList) based on maximum level (maxLevel):

listSubset <- function(x, maxLevel){
  lapply(x, function(ls){
    lapply(ls, function(df) df[df[[grep('Level', names(df), value = TRUE)]] < maxLevel, ])
  })
}
listSubset(x = gameList, maxLevel = 6)

Output:

$`FEB_gems`
$`FEB_gems`$`GAME1`
[1] GAME1_Class     GAME1_Race      GAME1_Alignment GAME1_Level     GAME1_Alive    
<0 rows> (or 0-length row.names)

$`FEB_gems`$GAME2
  GAME2_Class GAME2_Race GAME2_Alignment GAME2_Level GAME2_Alive
1     fighter        elf              NE           5           n
2      wizard   half-elf              CG           5           y
3      cleric        elf              CE           5           y
4        monk      human              NN           5           y
5        bard      dwarf              LG           5           y


$MAR_gems
$MAR_gems$`GAME3`
  GAME3_Class GAME3_Race GAME3_Alignment GAME3_Level GAME3_Alive
1      cleric        elf              LG           1           y
2   barbarian   half-elf              LG           1           y
3     warlock        elf              CE           1           y
4      ranger      human              LN           1           n
5        monk      dwarf              LG           1           y

$MAR_gems$GAME4
  GAME4_Class GAME4_Race GAME4_Alignment GAME4_Level GAME4_Alive
1     paladin        elf              CE           5           n
2     fighter   half-elf              CG           5           y
3      wizard        elf              CE           5           y
4    sorcerer      human              LN           5           y
5       rouge      dwarf              LG           5           y

All the functions are in base R, so no need to install and learn new packages.

Upvotes: 2

Dan
Dan

Reputation: 12074

I'd argue that life would be easier if you restructure your data, then use dplyr's filter to pull out what you want (or omit what you don't want). Assuming your original data is called foo:

# Load libraries
library(dplyr)
library(purrr)

# Remove one list
bar <- unlist(foo, recursive = FALSE)

# Get names of campaigns and games
campaign_games <- data.frame(do.call(rbind, strsplit(names(bar), "\\.")))

# Add campaigns and games numbers to data frames
ls_games <- pmap(list(campaign_games[, 1], campaign_games[, 2], bar), cbind)

# Rename all columns
ls_games <-  lapply(ls_games, function(x){names(x) <- c("Campaign", "Game_n", "Class", "Race", "Alignment", "Level", "Alive"); x})

# Convert to data frame
df <- bind_rows(ls_games)

# Look at result
head(df)

Now your data looks like this:

#   Campaign Game_n    Class  Race Alignment Level Alive
# 1 FEB_gems  GAME1  paladin human        NE     6     y
# 2 FEB_gems  GAME1  fighter   elf        CG     7     y
# 3 FEB_gems  GAME1   wizard   orc        CE     6     y
# 4 FEB_gems  GAME1 sorcerer human        NN     7     y
# 5 FEB_gems  GAME1    rouge gnome        LG     7     y
# 6 FEB_gems  GAME2  fighter   elf        NE     5     n

which is easy to handle. For example, pull those that are alive in game 1 of FEB gems and are level 7 or higher.

df %>% filter(Alive == "y", Campaign == "FEB_gems", 
              Level >= 7, Game_n == "GAME1")

#   Campaign Game_n    Class  Race Alignment Level Alive
# 1 FEB_gems  GAME1  fighter   elf        CG     7     y
# 2 FEB_gems  GAME1 sorcerer human        NN     7     y
# 3 FEB_gems  GAME1    rouge gnome        LG     7     y

Upvotes: 1

akrun
akrun

Reputation: 887391

If there are only two nested lists, and want different filtering conditions, apply on it individually and assign the output back to the list element. We loop through the master list with map and then apply the logical conditions

library(purrr)
library(dplyr)
lst2 <- map(lst1, ~  {
       .x[[1]] <- .x[[1]] %>%
                       filter_at(4, all_vars(. > 6))
       .x[[2]] <- .x[[2]] %>%
                      filter_at(5, all_vars(. == 'y'))
         .x
    })

Upvotes: 1

Related Questions