ggpmisc 0.2.13
Debugging ggplots

Pedro J. Aphalo

2016-11-25

Preliminaries

library(ggpmisc)
# library(ggplot2)
library(tibble)

We generate some artificial data.

set.seed(4321)
# generate artificial data
x <- 1:100
y <- (x + x^2 + x^3) + rnorm(length(x), mean = 0, sd = mean(x^3) / 4)
my.data <- data.frame(x, 
                      y, 
                      group = c("A", "B"), 
                      y2 = y * c(0.5,2),
                      block = c("a", "a", "b", "b"))

Introduction

The motivation for writing these stats and geoms is that at the moment it is in many cases not possible to set breakpoints inside the code of stats and geoms. This can make it tedious to see how these functions work, as one may need to add print statements to their source code to achieve this. I wrote these functions as tools to help in the development of this package itself, and as a way of learning myself how data are passed around within the different components of a ggplot object when it is printed.

The stats described in this vignette are very simple and print a summary of their data input to the console. In addition they also return a data frame containing labels suitable for plotting with geom “text” or geom “label”. However, starting from version 0.2.7 of the package the default geom is “null”. The values are listed to the console at the time when the ggplot object is printed.

As shown here, no other geom or stat is required, however in the remaining examples we include geom_point() to make the data on the plot visible.

ggplot(my.data, aes(x, y)) + stat_debug_group()
## [1] "Input 'data' to 'compute_group()':"
## # A tibble: 100 × 4
##        x          y PANEL group
## *  <dbl>      <dbl> <int> <int>
## 1      1 -27205.450     1    -1
## 2      2 -14242.651     1    -1
## 3      3  45790.918     1    -1
## 4      4  53731.420     1    -1
## 5      5  -8028.578     1    -1
## 6      6 102863.943     1    -1
## 7      7 -18547.282     1    -1
## 8      8  13080.521     1    -1
## 9      9  79924.325     1    -1
## 10    10 -44711.499     1    -1
## # ... with 90 more rows

In the absence of facets or groups we get just get the summary from one data frame.

ggplot(my.data, aes(x, y)) + geom_point() + stat_debug_group()
## [1] "Input 'data' to 'compute_group()':"
## # A tibble: 100 × 4
##        x          y PANEL group
## *  <dbl>      <dbl> <int> <int>
## 1      1 -27205.450     1    -1
## 2      2 -14242.651     1    -1
## 3      3  45790.918     1    -1
## 4      4  53731.420     1    -1
## 5      5  -8028.578     1    -1
## 6      6 102863.943     1    -1
## 7      7 -18547.282     1    -1
## 8      8  13080.521     1    -1
## 9      9  79924.325     1    -1
## 10    10 -44711.499     1    -1
## # ... with 90 more rows

ggplot(my.data, aes(x, y)) + geom_point() + stat_debug_panel()
## [1] "Input 'data' to 'compute_panel()':"
## # A tibble: 100 × 4
##        x          y PANEL group
##    <dbl>      <dbl> <int> <int>
## 1      1 -27205.450     1    -1
## 2      2 -14242.651     1    -1
## 3      3  45790.918     1    -1
## 4      4  53731.420     1    -1
## 5      5  -8028.578     1    -1
## 6      6 102863.943     1    -1
## 7      7 -18547.282     1    -1
## 8      8  13080.521     1    -1
## 9      9  79924.325     1    -1
## 10    10 -44711.499     1    -1
## # ... with 90 more rows

In the case of grouping then one data frame is summarized for each group in the ggplot object.

ggplot(my.data, aes(x, y, colour = group)) + geom_point() + 
  stat_debug_group()
## [1] "Input 'data' to 'compute_group()':"
## # A tibble: 50 × 5
##        x          y colour PANEL group
## *  <dbl>      <dbl> <fctr> <int> <int>
## 1      1 -27205.450      A     1     1
## 2      3  45790.918      A     1     1
## 3      5  -8028.578      A     1     1
## 4      7 -18547.282      A     1     1
## 5      9  79924.325      A     1     1
## 6     11  -2823.736      A     1     1
## 7     13 -78016.690      A     1     1
## 8     15 -74281.234      A     1     1
## 9     17   9903.674      A     1     1
## 10    19 -94022.623      A     1     1
## # ... with 40 more rows
## [1] "Input 'data' to 'compute_group()':"
## # A tibble: 50 × 5
##        x         y colour PANEL group
## *  <dbl>     <dbl> <fctr> <int> <int>
## 1      2 -14242.65      B     1     2
## 2      4  53731.42      B     1     2
## 3      6 102863.94      B     1     2
## 4      8  13080.52      B     1     2
## 5     10 -44711.50      B     1     2
## 6     12  23839.55      B     1     2
## 7     14  75601.96      B     1     2
## 8     16 104676.72      B     1     2
## 9     18 -68746.93      B     1     2
## 10    20 -39230.19      B     1     2
## # ... with 40 more rows

Without facets, we still have only one panel.

ggplot(my.data, aes(x, y, colour = group)) + geom_point() + 
  stat_debug_panel()
## [1] "Input 'data' to 'compute_panel()':"
## # A tibble: 100 × 5
##        x          y colour PANEL group
##    <dbl>      <dbl> <fctr> <int> <int>
## 1      1 -27205.450      A     1     1
## 2      2 -14242.651      B     1     2
## 3      3  45790.918      A     1     1
## 4      4  53731.420      B     1     2
## 5      5  -8028.578      A     1     1
## 6      6 102863.943      B     1     2
## 7      7 -18547.282      A     1     1
## 8      8  13080.521      B     1     2
## 9      9  79924.325      A     1     1
## 10    10 -44711.499      B     1     2
## # ... with 90 more rows

The data are similar, except for the column named after the aesthetic, for the aesthetics used for grouping.

ggplot(my.data, aes(x, y, shape = group)) + geom_point() + 
  stat_debug_group()
## [1] "Input 'data' to 'compute_group()':"
## # A tibble: 50 × 5
##        x          y  shape PANEL group
## *  <dbl>      <dbl> <fctr> <int> <int>
## 1      1 -27205.450      A     1     1
## 2      3  45790.918      A     1     1
## 3      5  -8028.578      A     1     1
## 4      7 -18547.282      A     1     1
## 5      9  79924.325      A     1     1
## 6     11  -2823.736      A     1     1
## 7     13 -78016.690      A     1     1
## 8     15 -74281.234      A     1     1
## 9     17   9903.674      A     1     1
## 10    19 -94022.623      A     1     1
## # ... with 40 more rows
## [1] "Input 'data' to 'compute_group()':"
## # A tibble: 50 × 5
##        x         y  shape PANEL group
## *  <dbl>     <dbl> <fctr> <int> <int>
## 1      2 -14242.65      B     1     2
## 2      4  53731.42      B     1     2
## 3      6 102863.94      B     1     2
## 4      8  13080.52      B     1     2
## 5     10 -44711.50      B     1     2
## 6     12  23839.55      B     1     2
## 7     14  75601.96      B     1     2
## 8     16 104676.72      B     1     2
## 9     18 -68746.93      B     1     2
## 10    20 -39230.19      B     1     2
## # ... with 40 more rows

If we use as geom "label" or "text" a debug summary is added to the plot itself, we can use other arguments valid for the geom used, in this case vjust.

ggplot(my.data, aes(x, y, shape = group)) + geom_point() + 
  stat_debug_group(geom = "label", vjust = c(-0.5,1.5))
## [1] "Input 'data' to 'compute_group()':"
## # A tibble: 50 × 5
##        x          y  shape PANEL group
## *  <dbl>      <dbl> <fctr> <int> <int>
## 1      1 -27205.450      A     1     1
## 2      3  45790.918      A     1     1
## 3      5  -8028.578      A     1     1
## 4      7 -18547.282      A     1     1
## 5      9  79924.325      A     1     1
## 6     11  -2823.736      A     1     1
## 7     13 -78016.690      A     1     1
## 8     15 -74281.234      A     1     1
## 9     17   9903.674      A     1     1
## 10    19 -94022.623      A     1     1
## # ... with 40 more rows
## [1] "Input 'data' to 'compute_group()':"
## # A tibble: 50 × 5
##        x         y  shape PANEL group
## *  <dbl>     <dbl> <fctr> <int> <int>
## 1      2 -14242.65      B     1     2
## 2      4  53731.42      B     1     2
## 3      6 102863.94      B     1     2
## 4      8  13080.52      B     1     2
## 5     10 -44711.50      B     1     2
## 6     12  23839.55      B     1     2
## 7     14  75601.96      B     1     2
## 8     16 104676.72      B     1     2
## 9     18 -68746.93      B     1     2
## 10    20 -39230.19      B     1     2
## # ... with 40 more rows

The summary function can be a user defined one, which allows lots of flexibility.

ggplot(my.data, aes(x, y)) + geom_point() + 
  stat_debug_group(summary.fun = summary)
## [1] "Input 'data' to 'compute_group()':"
##        x                y               PANEL       group   
##  Min.   :  1.00   Min.   : -94023   Min.   :1   Min.   :-1  
##  1st Qu.: 25.75   1st Qu.:  40345   1st Qu.:1   1st Qu.:-1  
##  Median : 50.50   Median : 154036   Median :1   Median :-1  
##  Mean   : 50.50   Mean   : 266433   Mean   :1   Mean   :-1  
##  3rd Qu.: 75.25   3rd Qu.: 422069   3rd Qu.:1   3rd Qu.:-1  
##  Max.   :100.00   Max.   :1077469   Max.   :1   Max.   :-1

ggplot(my.data, aes(x, y)) + geom_point() + 
  stat_debug_group(summary.fun = head)
## [1] "Input 'data' to 'compute_group()':"
##   x          y PANEL group
## 1 1 -27205.450     1    -1
## 2 2 -14242.651     1    -1
## 3 3  45790.918     1    -1
## 4 4  53731.420     1    -1
## 5 5  -8028.578     1    -1
## 6 6 102863.943     1    -1

ggplot(my.data, aes(x, y)) + geom_point() + 
  stat_debug_group(summary.fun = nrow)
## [1] "Input 'data' to 'compute_group()':"
## [1] 100

The default.

ggplot(my.data, aes(x, y)) + geom_point() + 
  stat_debug_group(summary.fun = as_data_frame)
## [1] "Input 'data' to 'compute_group()':"
## # A tibble: 100 × 4
##        x          y PANEL group
## *  <dbl>      <dbl> <int> <int>
## 1      1 -27205.450     1    -1
## 2      2 -14242.651     1    -1
## 3      3  45790.918     1    -1
## 4      4  53731.420     1    -1
## 5      5  -8028.578     1    -1
## 6      6 102863.943     1    -1
## 7      7 -18547.282     1    -1
## 8      8  13080.521     1    -1
## 9      9  79924.325     1    -1
## 10    10 -44711.499     1    -1
## # ... with 90 more rows

ggplot(my.data, aes(x, y)) + geom_point() + 
  stat_debug_group(summary.fun = head, summary.fun.args = list(n = 3))
## [1] "Input 'data' to 'compute_group()':"
##   x         y PANEL group
## 1 1 -27205.45     1    -1
## 2 2 -14242.65     1    -1
## 3 3  45790.92     1    -1

This next chunk showing how to print the whole data frame is not run as its output is more than 100 lines long as the data set contains 100 observations.

ggplot(my.data, aes(x, y)) + geom_point() + 
  stat_debug_group(summary.fun = function(x) {x})

With grouping, for each group the compute_group() function is called with a subset of the data.

ggplot(my.data, aes(x, y, colour = group)) + geom_point() + 
  stat_debug_group(summary.fun = head, summary.fun.args = list(n = 3))
## [1] "Input 'data' to 'compute_group()':"
##   x          y colour PANEL group
## 1 1 -27205.450      A     1     1
## 3 3  45790.918      A     1     1
## 5 5  -8028.578      A     1     1
## [1] "Input 'data' to 'compute_group()':"
##   x         y colour PANEL group
## 2 2 -14242.65      B     1     2
## 4 4  53731.42      B     1     2
## 6 6 102863.94      B     1     2

In this example with grouping and facets, within each panel the compute_group() function is called for each group, in total four times.

ggplot(my.data, aes(x, y, colour = group)) + geom_point() + 
  stat_debug_group(summary.fun = nrow) +
  facet_wrap(~block)
## [1] "Input 'data' to 'compute_group()':"
## [1] 25
## [1] "Input 'data' to 'compute_group()':"
## [1] 25
## [1] "Input 'data' to 'compute_group()':"
## [1] 25
## [1] "Input 'data' to 'compute_group()':"
## [1] 25

With facets, for each panel the compute_panel() function is called with a subset of the data that is not split by groups. For our example, it is called twice.

ggplot(my.data, aes(x, y, colour = group)) + geom_point() + 
  stat_debug_panel(summary.fun = nrow) +
  facet_wrap(~block)
## [1] "Input 'data' to 'compute_panel()':"
## [1] 50
## [1] "Input 'data' to 'compute_panel()':"
## [1] 50

Finally we show how geom_debug() can be used. First to print to the console the data as passed to geoms.

ggplot(my.data, aes(x, y, colour = group)) + geom_point() + 
  geom_debug(summary.fun = head)
## Input 'data' to 'geom_debug()':
##    colour x          y PANEL group
## 1 #F8766D 1 -27205.450     1     1
## 2 #00BFC4 2 -14242.651     1     2
## 3 #F8766D 3  45790.918     1     1
## 4 #00BFC4 4  53731.420     1     2
## 5 #F8766D 5  -8028.578     1     1
## 6 #00BFC4 6 102863.943     1     2

And also to print to the console the data returned by a stat.

ggplot(my.data, aes(x, y, colour = group)) + geom_point() + 
  stat_smooth(method = "lm",
             geom = "debug", 
             summary.fun = as_data_frame, 
             summary.fun.args = list())
## Input 'data' to 'geom_debug()':
## # A tibble: 160 × 8
##     colour         x          y      ymin       ymax       se PANEL group
##      <chr>     <dbl>      <dbl>     <dbl>      <dbl>    <dbl> <int> <int>
## 1  #F8766D  1.000000 -188136.10 -254710.6 -121561.62 33111.18     1     1
## 2  #F8766D  2.240506 -176933.14 -242260.5 -111605.80 32490.90     1     1
## 3  #F8766D  3.481013 -165730.18 -229819.0 -101641.36 31874.92     1     1
## 4  #F8766D  4.721519 -154527.23 -217386.7  -91667.76 31263.50     1     1
## 5  #F8766D  5.962025 -143324.27 -204964.1  -81684.46 30656.89     1     1
## 6  #F8766D  7.202532 -132121.31 -192551.7  -71690.88 30055.40     1     1
## 7  #F8766D  8.443038 -120918.36 -180150.3  -61686.39 29459.34     1     1
## 8  #F8766D  9.683544 -109715.40 -167760.5  -51670.30 28869.04     1     1
## 9  #F8766D 10.924051  -98512.44 -155383.0  -41641.90 28284.87     1     1
## 10 #F8766D 12.164557  -87309.48 -143018.6  -31600.41 27707.21     1     1
## # ... with 150 more rows

ggplot(my.data, aes(x, y, colour = group)) + geom_point() + 
  stat_peaks(span = NULL,
             geom = "debug", 
             summary.fun = as_data_frame, 
             summary.fun.args = list())
## Input 'data' to 'geom_debug()':
## # A tibble: 2 × 10
##    colour xintercept yintercept label     x       y PANEL group x.label
##     <chr>      <dbl>      <dbl> <chr> <dbl>   <dbl> <int> <int>   <chr>
## 1 #F8766D         95     984858    95    95  984858     1     1      95
## 2 #00BFC4        100    1077468   100   100 1077468     1     2     100
## # ... with 1 more variables: y.label <chr>

formula <- y ~ poly(x, 3, raw = TRUE)
ggplot(my.data, aes(x, y)) +
  stat_fit_residuals(formula = formula, 
                     geom = "debug",
                     summary.fun = as_data_frame, 
                     summary.fun.args = list())
## Input 'data' to 'geom_debug()':
## # A tibble: 100 × 6
##        x          y    y.resid y.resid.abs PANEL group
##    <dbl>      <dbl>      <dbl>       <dbl> <int> <int>
## 1      1 -23513.687 -23513.687   23513.687     1    -1
## 2      2 -11663.732 -11663.732   11663.732     1    -1
## 3      3  47289.460  47289.460   47289.460     1    -1
## 4      4  54175.231  54175.231   54175.231     1    -1
## 5      5  -8620.676  -8620.676    8620.676     1    -1
## 6      6 101247.937 101247.937  101247.937     1    -1
## 7      7 -21182.019 -21182.019   21182.019     1    -1
## 8      8   9425.407   9425.407    9425.407     1    -1
## 9      9  75240.365  75240.365   75240.365     1    -1
## 10    10 -50439.597 -50439.597   50439.597     1    -1
## # ... with 90 more rows

formula <- y ~ x + I(x^2) + I(x^3)
ggplot(my.data, aes(x, y)) +
  geom_point() +
  stat_fit_augment(method = "lm", 
                   method.args = list(formula = formula),
                   geom = "debug",
                   summary.fun = tibble::as_data_frame, 
                   summary.fun.args = list()) +
  stat_fit_augment(method = "lm", 
                   method.args = list(formula = formula),
                   geom = "smooth")
## Input 'data' to 'geom_debug()':
## # A tibble: 100 × 17
##         ymin     ymax          y     x I.x.2. I.x.3.    .fitted  .se.fit
##        <dbl>    <dbl>      <dbl> <dbl>  <dbl>  <dbl>      <dbl>    <dbl>
## 1  -48698.27 41314.74 -3691.7638     1      1      1 -3691.7638 22673.48
## 2  -44308.35 39150.51 -2578.9186     2      4      8 -2578.9186 21022.55
## 3  -40181.60 37184.51 -1498.5419     3      9     27 -1498.5419 19487.84
## 4  -36313.71 35426.09  -443.8109     4     16     64  -443.8109 18070.62
## 5  -32700.69 33884.88   592.0973     5     25    125   592.0973 16772.32
## 6  -29338.59 32570.60  1616.0056     6     36    216  1616.0056 15594.38
## 7  -26223.08 31492.56  2634.7369     7     49    343  2634.7369 14538.06
## 8  -23348.80 30659.03  3655.1141     8     64    512  3655.1141 13604.10
## 9  -20708.60 30076.52  4683.9600     9     81    729  4683.9600 12792.32
## 10 -18292.59 29748.78  5728.0975    10    100   1000  5728.0975 12101.20
## # ... with 90 more rows, and 9 more variables: .resid <dbl>, .hat <dbl>,
## #   .sigma <dbl>, .cooksd <dbl>, .std.resid <dbl>, y.observed <dbl>,
## #   t.value <dbl>, PANEL <int>, group <int>

formula <- y ~ x + I(x^2) + I(x^3)
ggplot(my.data, aes(x, y2, colour = group)) +
  geom_point() +
  stat_fit_augment(method = "lm", 
                   method.args = list(formula = formula),
                   geom = "debug",
                   summary.fun = tibble::as_data_frame, 
                   summary.fun.args = list()) +
  stat_fit_augment(method = "lm", 
                   method.args = list(formula = formula),
                   geom = "smooth")
## Input 'data' to 'geom_debug()':
## # A tibble: 100 × 19
##     colour      ymin     ymax .rownames           y     x I.x.2. I.x.3.
##      <chr>     <dbl>    <dbl>     <chr>       <dbl> <dbl>  <dbl>  <dbl>
## 1  #F8766D -41080.41 20399.43         1 -10340.4868     1      1      1
## 2  #F8766D -35273.92 17554.72         3  -8859.5995     3      9     27
## 3  #F8766D -30156.45 15352.63         5  -7401.9119     5     25    125
## 4  #F8766D -25719.57 13832.64         7  -5943.4676     7     49    343
## 5  #F8766D -21946.27 13025.65         9  -4460.3100     9     81    729
## 6  #F8766D -18793.72 12936.75        11  -2928.4828    11    121   1331
## 7  #F8766D -16175.63 13527.57        13  -1324.0295    13    169   2197
## 8  #F8766D -13958.28 14712.29        15    377.0064    15    225   3375
## 9  #F8766D -11978.53 16375.69        17   2198.5812    17    289   4913
## 10 #F8766D -10073.31 18402.61        19   4164.6515    19    361   6859
## # ... with 90 more rows, and 11 more variables: .fitted <dbl>,
## #   .se.fit <dbl>, .resid <dbl>, .hat <dbl>, .sigma <dbl>, .cooksd <dbl>,
## #   .std.resid <dbl>, y.observed <dbl>, t.value <dbl>, PANEL <int>,
## #   group <int>

The package also defines a "null" geom, which is used as default by the debug stats described above. Currently this geom is similar to the recently added ggplot2::geom_blank().

ggplot(my.data, aes(x, y, colour = group)) + geom_null()