2

I am trying to use purr map function or its variants to map data across multiple functions ( in my case R-Shiny functions). I am reading the parameters from example.json.

{
  "Section_1": {
    "MainHeader": [{
      "School": "Montessori"
    }],
    "boxitems": [{
        "tabName": "id1",
        "box": [{
            "title": "Students graph",
            "custofun": ["Bob", "Dan", "Sean"]
          },
          {
            "title": "Teacher graph",
            "custofun": ["Robinson"]
          }
        ]
      },
      {
        "tabName": "id2",
        "box": [{
            "title": "Students graph",
            "custofun": ["Felix", "Helix", "Alex"]
          },
          {
            "title": "Teacher graph",
            "custofun": ["Phelix"]
          }
        ]
      }
    ]
  }
}

I read this into df format

json <- fromJSON("example.json")

I am trying to generate the tabItem's and its box'es dynamically by wrapping them in map function, for example to just map on tabItem values I can use map.

 map(unique(df$id), ~ tabItem(tabName = .x)) 

This would generate the html code for tabItem with all the list of ID's I have in the json file. For the json structure I have I need to traverse through ID -> list -> list. And, pass the respective input parameters to tabItem, box Tried the other variants like pmap but could not solve it. How to use purr map functions recursively in a data frame of this data structure?

Here is my attempt

json$Section_1$boxitems %>% as.tibble() # to check the strucutre
df <- json$Section_1$boxitems %>% select(tabName,box)
df$box <- setNames(df$box,df$tabName)

BoxCustomFunc <- function(tabName,box) {
   map(tabName , ~ tabItem(tabName = .x),
   map2(x = box, y = box[tabName],
         box(title = .x$title, 
      column(width = 2, get(.y$custofun)(tabName)))
    ))
}

The current output below. What I get is the tabItem, what is missing is the box and column html output. It seems the map2 does not even render.

[[1]]
<div role="tabpanel" class="tab-pane" id="shiny-tab-id1"></div>

[[2]]
<div role="tabpanel" class="tab-pane" id="shiny-tab-id2"></div>
Carl
  • 4,232
  • 2
  • 12
  • 24
user5249203
  • 4,436
  • 1
  • 19
  • 45
  • 1
    A couple things to make this [reproducible & minimal](https://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example): one, we'll need to see a representative sample of data, not a text printout of it, since you're working on an issue about data types; and two, this issue isn't *actually* about Shiny, so you don't need references to Shiny here – camille Oct 11 '18 at 19:11
  • Thanks for pointing out, i shared the json file I have this structure in. Hope this helps. – user5249203 Oct 11 '18 at 19:27

2 Answers2

1

I recommend putting everything together into a single, flat data frame:

df <- fromJSON( "example.json" )$Section_1$boxitems %>% as.tibble() %>% 
  unnest() %>% unnest() %>% mutate( Width = rep(c(2, 12, 4, 12), 2) )
# # A tibble: 8 x 4
#   tabName title         custofun Width
#   <chr>   <chr>         <chr>    <dbl>
# 1 id1     Student graph Bob          2
# 2 id1     Student graph Dan         12
# 3 id1     Student graph Sean         4
# 4 id1     Teacher graph Robinson    12
# 5 id2     Student graph Felix        2
# 6 id2     Student graph Helix       12
# 7 id2     Student graph Alex         4
# 8 id2     Teacher graph Phelix      12

The first step is to map your character names of functions to the actual functions:

## Assuming that Felix, Helix, Alex and Phelix are defined
X <- df %>% mutate_at( "custofun", map, rlang::parse_expr ) %>%
  mutate_at( "custofun", map, rlang::eval_tidy )
# # A tibble: 8 x 4
#   tabName title         custofun Width
#   <chr>   <chr>         <list>   <dbl>
# 1 id1     Student graph <fn>         2
# 2 id1     Student graph <fn>        12
# ...

Working from the inside outward, you can now systematically apply map2 to generate your shiny elements (functions applied to ID -> column -> box -> tab):

Y <- X %>% mutate( fres = invoke_map(custofun, tabName) ) %>%
  mutate( Col = map2(Width, fres, column) ) %>%
  group_by( tabName, title ) %>%
  summarize_at( "Col", list ) %>%
  mutate( Box = map2(title, Col, ~box(title=.x, .y)) ) %>%
  summarize_at( "Box", list ) %>%
  transmute( Tab = map2(tabName, Box, ~tabItem(tabName = .x, .y)) )
# # A tibble: 2 x 1
#   Tab            
#   <list>         
# 1 <S3: shiny.tag>
# 2 <S3: shiny.tag>

Y$Tab[[1]] should now match the HTML you produced "by hand". (Minus the discrepancy between "Student graph" in JSON and "Students graph" in the code.)

Artem Sokolov
  • 13,196
  • 4
  • 43
  • 74
  • 1
    This is absolutely fantastic. Thank you very much. I think, for my real data I would like to control the width from JSON itself. It varies across plots – user5249203 Oct 15 '18 at 20:54
  • Is there a way I can use lappy or map to extract the Y$Tab elements? simple print, throws an error expecting shiny.tag object. `map(lst,`[[`)`, expects an index, if I pass `seq_along(lst)` as index, it throws an error recursive fails at 2. How do I print shiny.tags recursively ? – user5249203 Oct 19 '18 at 17:32
  • Just `Y$Tab` prints both tags for me. – Artem Sokolov Oct 19 '18 at 17:35
  • It does in the source, but when I render on Rshiny it throws `Error in FUN(X[[i]], ...) : Expected an object with class 'shiny.tag'.` – user5249203 Oct 19 '18 at 17:38
  • Sounds like a separate issue. Maybe `shiny` doesn't deal well with elements stored inside lists? I would recommend posting a separate question with a minimal example where you create a list of `tabItem`s by hand and show the error that `shiny` gives you. Let me know if you can't reproduce the issue with a "by hand" list. It might mean there's some funky interplay between `shiny` and `tibble`. – Artem Sokolov Oct 19 '18 at 19:26
  • Yeah, looks like it is a shiny issue. Will post it separately. Thank you. BTW, if i call individual list `Y$Tab[[1]], Y$Tab[[2]]` . This works and renders properly. So, I guess it is not with tibble. – user5249203 Oct 19 '18 at 19:56
0

Had a bit of difficulty understanding exactly what you need out of it but hopefully you can take what you need from this.

What I did in brief is turn the unstructured data into a tidy tall dataframe and loop through that to generate the r shiny code.

1- I run through and grab every boxitem dataframe and add a new column that adds the boxitem's tabName to it.

2- Each row of the boxitem dataframes has a list of custofuns and the unnest function splits them into multiple rows.

3- I combined the boxitem dataframes into one big dataframe that you could manipulate as you see fit.

library(jsonlite)
library(tidyverse)

json <- fromJSON("example.json")

listOfGraphs <- apply(json$Section_1$boxitems, 1, function(x) x$box %>% mutate(tabName = x$tabName) ) 

listOfTabNames <- lapply(listOfGraphs, function(y) unnest(y))

listOfColumns <- bind_rows(listOfTabNames)

4- This generates the r shiny code in string format. We are faced with the issue of how wide your columns are going to be if there are lots of them. It loops through each student and creates a column for them.

listOfTabItems <- lapply(listOfTabNames, 
       function(x) paste(
         "tabItem(
  tabName = '",x$tabName[1],"',
  box(        
    title = 'Students graph',",
         apply(subset(x, title=="Student graph"), 1, function(y) paste0("column(width = 4, ",y[3],"('",y[2],"'))")), collapse = ", ",
         "),
  box(        
    title = 'Teacher graph',
   column(width = 12, ",subset(x, title=="Teacher graph")$custofun[1],"('",x$tabName[1],"'))
  )
)"
         )
       )
Sahir Moosvi
  • 549
  • 2
  • 21
  • what is the listofBoxitems ? solutions throws errors. I am trying to read the parameters that are input to Rshiny functions. Rshiny functions generate html tags. so, the desired output is html tags. – user5249203 Oct 13 '18 at 18:39
  • My apologies I renamed my variables. Fixed it here. Ok so step 4 should be useful then – Sahir Moosvi Oct 13 '18 at 22:16
  • I don't think it is a solution I am looking for. If you see my attempt above, where I created a `BoxCustomFunc` , I pass the JSON file contents to this function. Moreover, you used paste it messes up with the html output with `\n` and other special characters. I have an example output above. I will paste the expected html above. – user5249203 Oct 14 '18 at 16:51