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]])
  }
}
Tom
  • 2,173
  • 1
  • 17
  • 44
  • @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. – Tom May 03 '22 at 09:41
  • @user2554330 Thanks for the pointers. Sorry, it already took some time to try to recreate the example. I will try to clean it up. – Tom May 03 '22 at 09:44
  • @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.. – Tom May 03 '22 at 09:50
  • @user2554330 I have tried to make my question clearer.. – Tom May 03 '22 at 10:24
  • @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`. – Tom May 03 '22 at 13:31

2 Answers2

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)
```
Andrew Brown
  • 1,045
  • 6
  • 13
  • 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? – Tom May 06 '22 at 06:57
  • 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. – Andrew Brown May 06 '22 at 07:29
  • 1
    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()` – Andrew Brown May 06 '22 at 07:36
  • 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). – Tom May 06 '22 at 07:40
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.

Mikko Marttila
  • 10,972
  • 18
  • 31