Listing to knitr environment (R-markdown error when using list2env in R chunks)
Asked Answered
J

2

2

Edit: I figured out that the issue stems from the yaml code. It comes from applying this solution to dynamically name files. I believe it creates a parent environment within the knitr code, because of which list2env creates unexpected behaviour

Just to be clear, the code below runs perfectly in R.

I am using list2env with envir = .GlobalEnv in my knitr code. This does not work because it creates objects outside the knitr environment (I am getting an error like this when I run my R code in R-markdown, because the objects created only exist in the global environment). I am trying to either find an alternative to list2env or a way to tell list2env to list to the current knitr environment.

Preparation in R

# setwd to an empty folder
setwd("C:/../testing_environment")

library(writexl)
library(readxl)

# Example data
write_xlsx(mtcars, "mt_cars.xlsx")
write_xlsx(mtcars, "mt_car_s.xlsx")

Code to knit in R-markdown (link for installation):

---
title: thetitle
author: myinititals
output:
  word_document
date: "`r Sys.Date()`"
knit: (function(inputFile, encoding) { 
          out_dir <- 'test';
          rmarkdown::render(inputFile,
                            encoding=encoding, 
                            output_file=file.path(dirname(inputFile), out_dir, 'analysis.docx')) })
```{r}

library(writexl)
library(readxl)

setwd("C:/../testing_environment")

paths <- list.files(pattern="*.xlsx")
read_all_sheets <- 
  function(path) sapply(excel_sheets(path), read_excel, path = path, USE.NAMES = TRUE, simplify = FALSE)
xl_list <- sapply(paths, read_all_sheets, USE.NAMES = TRUE, simplify = FALSE)

# List to environment - ISSUE
for (i in seq_along(xl_list)) {
  list2env(xl_list[[i]], envir = .GlobalEnv)
}

# THIS LINE CRASHES THE CODE
names(mt_car_s)  

```

Error:

enter image description here

I have checked what happens by removing names(mt_car_s) and doing:

list2env(xl_list, envir = .GlobalEnv)
## <environment: R_GlobalEnv>
ls()
## [1] "i"               "paths"           "read_all_sheets"          
## [4] "xl_list"

But it simply does not list the objects within xl_list.

Is there any way to get these files into the environment? For example code to write to the created knitr environment? If not, what are other possible solutions to prevent this behaviour?

EDIT 2, for a list of list you can do:

for (i in seq_along(xl_list)) {
    n <- names(xl_list[[i]])
    print(n)
    for (j in seq_along(n)) {
      assign(n[j], xl_list[[i]][[j]])
  }
}
Janey answered 3/5, 2022 at 9:20 Comment(5)
@user2554330 Thank you for your comment. In that case I made a mistake in the example. The problem persists for named lists (i.e. my code runs perfectly within R, but breaks down when putting it in knitr. I will try to adapt the example though.Janey
@user2554330 Thanks for the pointers. Sorry, it already took some time to try to recreate the example. I will try to clean it up.Janey
@user2554330 Are you sure I can construct the list directly? How do I keep list2env from failing if I create the list outside knitr? If I create it within knitr, the problem does not exist in the first place..Janey
@user2554330 I have tried to make my question clearer..Janey
@user2554330 xl_list is now a named list, and I am still getting the exact same error.. (I am pretty sure it was before as well). In any case, this is the actual code I am running, it runs perfectly fine in R (as did the code before) but it will not run in knitr.Janey
B
2

The environment in which an R Markdown document is rendered can be changed with the envir option to rmarkdown::render(). In general, there's no guarantee that the rendering environment is the global environment or a child of it -- so you should not try to assign to .GlobalEnv.

Instead, assign to the current environment by replacing .GlobalEnv with environment() in the list2env() call.

Believe answered 6/5, 2022 at 9:41 Comment(1)
Thanks again Mikko, this saved me a whole lot of trouble!Janey
C
2

Using assign() (by default into current environment) can probably achieve what you want. Here I remove the ".xlsx" from the names in your list xl_list so that the first level of the nested list has the names that you want to become objects in your current environment, and the names in those objects are the sheet names

---
title: "Sample Document"
output:
  word_document
---
    
```{r}
library(writexl)
library(readxl)

setwd("C:/../testing_environment")

paths <- list.files(pattern="*.xlsx")
read_all_sheets <-function(path) sapply(excel_sheets(path), read_excel, path = path, USE.NAMES = TRUE, simplify = FALSE)
xl_list <- sapply(paths, read_all_sheets, USE.NAMES = TRUE, simplify = FALSE)

# remove file extension from e.g. mt_car_s.xlsx
names(xl_list) <- gsub("\\.xlsx", "", names(xl_list))

# assign each element of the list 
# (itself a list of sheets/data frames)
# an object name in current environment
for (i in seq_along(xl_list)) {
  n <- names(xl_list[i])
  assign(n, xl_list[[i]])
}

names(mt_car_s)
names(mt_car_s$Sheet1)
```
Crossland answered 6/5, 2022 at 6:21 Comment(4)
Thank you for your answer Andrew! In reality I my data is a list of lists. I tried adapting to your code to my situation, but I am making a mistake (see EDIT 2). Do you know what I am doing wrong?Janey
Try n <- names(xl_list[[i]][j]). The names of each j-th element in each element i become the object names in your current environment. Note only a single set of square brackets around the j, this means you are getting the names of the list object, rather than the object stored in the element of the list object.Crossland
In fact, doing that in your original code e.g. list2env(xl_list[i], envir = .GlobalEnv) will work. But the name of the object will be mt_car_s.xlsx without for example renaming as I did above with gsub()Crossland
Thanks Andrew, I figure it out (with a slightly different approach than yours, see edit 2 again). Because the sheets do not have the .xlsx extension it works fine. I am going to try your list2env solution as well, but I am a bit skeptical about the list2env approach (which has my preference though, so will try that now).Janey
B
2

The environment in which an R Markdown document is rendered can be changed with the envir option to rmarkdown::render(). In general, there's no guarantee that the rendering environment is the global environment or a child of it -- so you should not try to assign to .GlobalEnv.

Instead, assign to the current environment by replacing .GlobalEnv with environment() in the list2env() call.

Believe answered 6/5, 2022 at 9:41 Comment(1)
Thanks again Mikko, this saved me a whole lot of trouble!Janey

© 2022 - 2024 — McMap. All rights reserved.