Rapporter / pander

An R Pandoc Writer: Convert arbitrary R objects into markdown
http://rapporter.github.io/pander/
Open Software License 3.0
294 stars 66 forks source link

Print error message and backtrace when using R5 API #119

Closed vlsi closed 10 years ago

vlsi commented 10 years ago

Here's testcase: the chart is using z column that is intentional so ggplot will fail when trying to render the chart.

Note that report.md includes no signs of the error.

The same applies for Pandoc.brew: it does not provide backtrace and it just tells you "there is an error in BRCODES"

qwe <- function() {
       d <- data.table(x=2,y=3)
       r <- Pandoc$new("Author", "")
       r$add(ggplot(d, aes(x,z))+geom_point())
       r$show()
       str(r)
       }
>  qwe()

 written by *Author* at *Mon Sep 15 18:15:19 2014*

  This report holds 1 block(s). 

---

---

Proc. time:  0.013 seconds. 

Reference class 'Pandoc' [package "pander"] with 6 fields
 $ author   : chr "Author"
 $ title    : chr ""
 $ date     : chr "Mon Sep 15 18:15:19 2014"
 $ body     :List of 1
  ..$ :List of 6
  .. ..$ src   : chr "ggplot(d, aes(x, z)) + geom_point()"
  .. ..$ result: NULL
  .. ..$ output: NULL
  .. ..$ type  : chr "error"
  .. ..$ msg   :List of 3
  .. .. ..$ messages: NULL
  .. .. ..$ warnings: NULL
  .. .. ..$ errors  : chr "object 'z' is not found"
  .. ..$ stdout: NULL
  .. ..- attr(*, "class")= chr "evals"
 $ format   : chr "pdf"
 $ proc.time: num 0.013
 and 17 methods, of which 5 are possibly relevant:
   add, add.paragraph, export#envRefClass, initialize, show#envRefClass
daroczig commented 10 years ago

Cool, thanks, I'll add this feature for R5 in the next few days as well. Until then, you can use the brew version with <%= ... %> type of chunks, which shows error messages. E.g.:

> Pandoc.brew(text = '
+ <%=
+ d <- data.table(x=2,y=3)
+ ggplot(d, aes(x,z))+geom_point()
+ %>')

 **ERROR**^[could not find function "data.table"] **ERROR**^[could not find function "ggplot"] 
vlsi commented 10 years ago

Until then, you can use the brew version with <%= ... %> type of chunks

The thing is I have loops in my report: "for given scenario, list details on the performance of steps of that scenario".

There is no <%loop.over.xyz kind of tag, thus I have to resort to <% .. %>. <%..%> is not very error-friendly, thus I switch to R5.

I will definitely survive a few days or weeks with the current state of affairs. At least R5 + approach of "always use just $add.paragraph" seems to show error messages at the cost of more verbose report code (e.g. explicit pandoc.image.return)

daroczig commented 10 years ago

What about this example:

> Pandoc.brew(text = '
+ <% for (i in 1:3) { %>
+ 
+ <%=
+ ggplot(d, aes(x,z))+geom_point()
+ %>
+ 
+ <% } %>')

 **ERROR**^[could not find function "ggplot"]

 **ERROR**^[could not find function "ggplot"]

 **ERROR**^[could not find function "ggplot"]

You can include <%= ... %> chunks between <% ... %> loops.

vlsi commented 10 years ago

That is what I do.

However <% ... %> still includes some code (e.g. loops, code to slice and dice data for a given loop iteration) and this code fails from time to time.

daroczig commented 10 years ago

Why don't you move everything out of <% ... %> into a separate <%= ... %> chunk? The <% ... %> should not really contain anything beside for or if statements, and all operations should rather be done in a previous <%= ... %> chunk.

vlsi commented 10 years ago

Example: 1) Prepare data for a particular step (filter raw data from the overall raw set of measurements) 2) Render top 5 samples from the prepared set 3) Render top 5 samples from the prepared set 4) Plot samples as a chart

I feel I like to prepare the data just once and render it multiple times.

Here's the sample:

<%
setkey(src$ui, "step")
cases <- src$summary[category=="UI" & scenario=="Overall" & !is.na(duration)][order(-stddev), ]
if (nrow(cases) > 0) {
apply(cases, 1, function(it) {
%>
## <%= it[["step"]] %>
Category: <%= it[["category"]] %>

<%
.e <- environment()
d <- src$ui[step==it[["step"]]]
d <- d[order(-duration), ]
nfrlines <- data.table(
  time=src$overview$start.time,
  duration=as.numeric(c(it[["required.time90"]], it[["duration90"]])),
  label=c(paste0("Response time NFR (", it[["required.time90"]], "s)"), paste0("90% line (", it[["duration90"]], "s)"))
)
%>
<%=
 ggplot(d, aes(x=time, y=duration, color=scenario))+geom_hline(data=nfrlines, aes(yintercept=duration)) + geom_text(data=nfrlines,aes(time, duration, label=label), color="#000000", vjust=-0.2, hjust=0) + geom_point()+ggtitle(paste0(it[["step"]], "\n", "response time over time")) + scale_color_discrete(name="Scenario") + scale_y_continuous("Response time, seconds", limits=lims)+scale_x_continuous(paste0("Time in ", reportTZ, " time zone"), trans=time_trans(tz=reportTZ), limits=c(src$overview$start.time, src$overview$stop.time))+facet_grid(success~., labeller=function(var, x) ifelse(x, "Success", "Failure")) + twoCol %>

### Top 5 slowest entries {.exclude_from_toc}
<%=
 pandoc.table.return(
 head(d[, list("Timestamp"=time, "Time since start"=time-src$overview$start.time, "Scenario"=scenario, "Customer ID"=customer.id, "ICOMS site"=site.id, "Duration, sec"=duration, "Result"=ifelse(success, "Success", "Failure"))], 5)
   , justify=c("left", "left", "left", "left", "center", "right", "left"))
%>

### Top 5 entries approaching 95% line {.exclude_from_toc}

<%=
 pandoc.table.return(
 head(d[floor(nrow(d)*0.05):floor(nrow(d)*0.05+4), list("Timestamp"=time, "Time since start"=time-src$overview$start.time, "Scenario"=scenario, "Customer ID"=customer.id, "ICOMS site"=site.id, "Duration, sec"=duration, "Result"=ifelse(success, "Success", "Failure"))], 5)
   , justify=c("left", "left", "left", "left", "center", "right", "left"))
%>

### Top 5 entries approaching 90% line {.exclude_from_toc}

<%=
 pandoc.table.return(
 head(d[floor(nrow(d)*0.1):floor(nrow(d)*0.1+4), list("Timestamp"=time, "Time since start"=time-src$overview$start.time, "Scenario"=scenario, "Customer ID"=customer.id, "ICOMS site"=site.id, "Duration, sec"=duration, "Result"=ifelse(success, "Success", "Failure"))], 5)
   , justify=c("left", "left", "left", "left", "center", "right", "left"))
%>

### Top 5 fastest entries {.exclude_from_toc}
<%=
 pandoc.table.return(
 tail(d[, list("Timestamp"=time, "Time since start"=time-src$overview$start.time, "Scenario"=scenario, "Customer ID"=customer.id, "ICOMS site"=site.id, "Duration, sec"=duration, "Result"=ifelse(success, "Success", "Failure"))], 5)
   , justify=c("left", "left", "left", "left", "center", "right", "left"))
%>

<% })} %>
daroczig commented 10 years ago

Ah, okay, I get what you mean. I'd not call apply with an anonymous function here, there's a lot easier design without the need to call pandoc.table.return etc. Probably I miss something obvious, but what about simply looping through nrow(cases)? So instead:

<%
setkey(src$ui, "step")
cases <- src$summary[category=="UI" & scenario=="Overall" & !is.na(duration)][order(-stddev), ]
if (nrow(cases) > 0) {
apply(cases, 1, function(it) {
%>

Something like:

<%=
setkey(src$ui, "step")
cases <- src$summary[category=="UI" & scenario=="Overall" & !is.na(duration)][order(-stddev), ]
%>
<%
if (nrow(cases) > 0) {
  for (it in 1:nrows(cases)) {
%>

If you could post a minimal reproducible example with some minimal dataset, I'd be happy to come up with a working demo.

vlsi commented 10 years ago

Probably I miss something obvious, but what about simply looping through nrow(cases)?

Initially I thought apply was somehow idiomatic-r. After some experiments I figured out that apply fails dismally in case of empty input list, thus I just fixed that with if (nrow()>0. Now I am just lazy to rewrite it since it works and it is not extra verbose.

Note that I can fetch data from iterator as it["required.time90"], while in for(it in 1:nrows(...)) style loop you'll have just the index. That's not a big deal, but in general the absence of "row-by-row iterator" was unexpected for me.

If you could post a minimal reproducible example with some minimal dataset, I'd be happy to come up with a working demo.

Will see if I can put it together.

design without the need to call pandoc.table.return etc

I use explicit pandoc.table.return for two main reasons: 1) Column alignment. It is not just "numerics right aligned", but sometimes I want text columns right aligned or centered. 2) Transposed tables like the following (as far as I can remember I have to specify style="rmarkdown" otherwise it looks bad):

pandoc.table.return(t(data.table(
    "Date" = "26/07/2013"
  , "Project" = "Acme load test"
  , "Test type" = "Scalability"
  , "Version test" = "acme_build_2.zip"
  , "Hardware layout" = "1 app server + 1 database, SW loadbalancer at app machine"
 )), style="rmarkdown", justify=c("left"))
daroczig commented 10 years ago

Will see if I can put it together.

That would be very useful, thank you!

1) Column alignment. It is not just "numerics right aligned", but sometimes I want text columns right aligned or centered.

What about panderOptions('table.alignment.default')?

2) Transposed tables like the following (as far as I can remember I have to specify style="rmarkdown" otherwise it looks bad):

What about panderOptions('table.style')?

E.g.:

> library(data.table)
data.table 1.9.2  For help type: help("data.table")
> panderOptions('table.alignment.default', 'left')
> panderOptions('table.style', 'rmarkdown')

> Pandoc.brew(text = '<%=
+ t(data.table(
+     "Date" = "26/07/2013"
+   , "Project" = "Acme load test"
+   , "Test type" = "Scalability"
+   , "Version test" = "acme_build_2.zip"
+   , "Hardware layout" = "1 app server + 1 database, SW loadbalancer at app machine"
+  ))
+ %>')

|                       |                                                           |
|:---------------------:|:----------------------------------------------------------|
|       **Date**        | 26/07/2013                                                |
|      **Project**      | Acme load test                                            |
|     **Test type**     | Scalability                                               |
|   **Version test**    | acme_build_2.zip                                          |
|  **Hardware layout**  | 1 app server + 1 database, SW loadbalancer at app machine |

To also set the default alignment for the row.names, see table.alignment.rownames in the options.

daroczig commented 10 years ago

Quick demo:

> r <- Pandoc$new()
> r$add(ggplot2::ggplot(foobar))
> r$add(warning('foobar'))
> r$add(thereIsNoSuchFunctoin())
> r

Anonymous's report
==================
 written by *Anonymous* at *Tue Sep 16 01:30:07 2014*

  This report holds 3 block(s). 

---

**ERROR:** object 'foobar' not found
**WARNING:** foobar
**ERROR:** could not find function "thereIsNoSuchFunctoin"
---

Proc. time:  0.038 seconds.