Get colnames from objects in global environment (With specific pattern), then just return what's new

Viewed 44

So I have a bunch of intermediate data frame objects that are numbered in my global environment according to their order. i.e. IRIS1_St, IRIS2_Db, IRIS1_Start, IRIS2_FIXAR, IRIS4_Change, IRIS10_bananas

I have worked out how to extract these and return the number of row and number of columns without keeping a list column (see below), and technically I have extracted the column names. But I can't for the life of me work out how to turn this colname column into something that isn't a list so I can then compare the lag value and return a simpler column that shows what's new. I've tried data.table(), data.frame(), as.character() and then a str_replace_all() to turn it into a vector. But nothing seems to work, which seems to be because I suck at using lists!


library(dplyr)
library(purrr)
library(stringr)
IRIS1_St <- iris
IRIS2_Db <- IRIS1_St %>% 
  mutate(Petal.Length2 = Petal.Length*2)

IRIS3_Sum <- IRIS2_Db %>%  
  mutate(Sepal.sum = sum(Sepal.Length, Sepal.Width)) 


IRIS4_Change <- IRIS3_Sum %>% 
  mutate(SL.Change = Sepal.Length - lag(Sepal.Length)) %>%  filter(Petal.Length >=4)


IRIS10_bananas <- IRIS4_Change %>% mutate( bananas = case_when(Sepal.Length >6 ~ "BANANAS!!"))

Obj_Size <- grep("^IRIS",names(.GlobalEnv),value=TRUE) %>%
  na.omit() %>%
  mget(envir = globalenv()) %>%
  {OS <<-.} %>% 
  map_df(nrow) %>% 
  pivot_longer(1:max(ncol(.)), names_to = "Obj_name", values_to = "nrow") %>% 
  left_join(OS %>% 
              map_df(ncol) %>% 
              pivot_longer(1:max(ncol(.)), names_to = "Obj_name", values_to = "ncol")
  ) %>%  
  data.frame(OS %>% 
               lapply(colnames) %>% 
               data.table()) %>% 
  mutate(number = as.numeric(replace_na(str_extract(Obj_name,  "(?i)(?<=IRIS\\D{0,1})\\d+"), 0))) %>% 
  arrange(number, Obj_name) %>% 
  select(-number) %>% data.frame() %>% 
  rename(colnames = '.') 
   
#just to seperate out the colname extraction I've done so far
OST <- OS %>%  lapply(colnames) %>% data.table()

to extract what's new I've tried the following, but it's screwing up because of the fact that I've made a list.

 Obj_Size_New <- Obj_Size %>% 
   mutate(lag_col = as.character(lag(colnames)),
          new_col = setdiff(as.character(colnames), lag_col))

Expected output is as follows;

  Obj_name        nrow ncol   new_col
1       IRIS1_St  150    5    Sepal.Length, Sepal.Width, Petal.Length, Petal.Width, Species
2       IRIS2_Db  150    6    Petal.Length2
3      IRIS3_Sum  150    7    Sepal.sum
4   IRIS4_Change  89     8    SL.Change
5 IRIS10_bananas  89     9    bananas
                           

I rewrote the above using the advise from akrun below;

library(dplyr)
library(purrr)
library(stringr)
library(tibble)

Obj_Size <- grep("^IRIS",names(.GlobalEnv),value=TRUE) %>% #ID all objects in GE starting with "IRIS"
  na.omit() %>% 
  mget(envir = globalenv()) %>% #Use base R to get them
{OS <<-.} %>% #create intermediate object in GE to join to later
  map_df(nrow) %>% #Map nrow using purrr
  pivot_longer(1:max(ncol(.)), names_to = "Obj_name", values_to = "nrow") %>% #pivot so it's readable
  left_join(OS %>% #repeat with ncol and join back to dataset
              map_df(ncol) %>%  
              pivot_longer(1:max(ncol(.)), names_to = "Obj_name", values_to = "ncol")
  ) %>%  
left_join(OS %>% #repeat with colnames
               map(colnames) %>% 
              enframe() %>%  #create 2 col dataframe
              rename(Obj_name = name, 
                     colnames = value)) %>% 
  mutate(number = as.numeric(replace_na(str_extract(Obj_name,  "(?i)(?<=IRIS\\D{0,1})\\d+"), 0))) %>% #extract number after IRIS in object name so we can order correctly even when we get to 10 as when ordered by name with 10 it puts it after 1.
  arrange(number, Obj_name) %>%
  select(-number) %>% 
  data.frame() %>% 
  mutate (new_col = map2_chr(colnames, lag(colnames), ~toString(setdiff(.x, .y)))) #Id changes between colnames and only return anything new. 
   
2 Answers

We can use map2 to do the setdiff here to return a list column by comparing the list column with the lag of the list column

library(dplyr)
library(purrr)
Obj_Size %>% 
       mutate(new_col = map2(colnames, lag(colnames), setdiff), colnames = NULL)

-output

#          Obj_name nrow ncol                                                       new_col
#1       IRIS1_St  150    5 Sepal.Length, Sepal.Width, Petal.Length, Petal.Width, Species
#2       IRIS2_Db  150    6                                                 Petal.Length2
#3      IRIS3_Sum  150    7                                                     Sepal.sum
#4   IRIS4_Change   89    8                                                     SL.Change
#5 IRIS10_bananas   89    9                                                       bananas

If it needs to be a character column, use

library(stringr)
Obj_Size %>%
        mutate(new_col = map2_chr(colnames, lag(colnames), 
             ~ str_c(setdiff(.x, .y), collapse=", ")), colnames = NULL)

-output

#      Obj_name nrow ncol                                                       new_col
#1       IRIS1_St  150    5 Sepal.Length, Sepal.Width, Petal.Length, Petal.Width, Species
#2       IRIS2_Db  150    6                                                 Petal.Length2
#3      IRIS3_Sum  150    7                                                     Sepal.sum
#4   IRIS4_Change   89    8                                                     SL.Change
#5 IRIS10_bananas   89    9                                                       bananas

Or using base R with Map

Obj_Size$new_col <- Map(setdiff, Obj_Size$colnames, c(NA, head(Obj_Size$colnames,-1)))

Since you have a list in colnames you can use map variants of purrr :

library(dplyr)
library(purrr)

Obj_Size %>%
  mutate(new_col = map2_chr(colnames, lag(colnames), ~toString(setdiff(.x, .y))))

Where the new_col looks like the following :

#                                                        new_col
#1 Sepal.Length, Sepal.Width, Petal.Length, Petal.Width, Species
#2                                                 Petal.Length2
#3                                                     Sepal.sum
#4                                                     SL.Change
#5                                                       bananas

In base R you can use mapply :

Obj_Size$new_col <- mapply(function(x, y) toString(setdiff(x, y)), 
              Obj_Size$colnames, c(NA, Obj_Size$colnames[-nrow(Obj_Size)]))
Related