ggplot не может нарисовать гладкую игру, используя facet_wrap и групповую астетику - PullRequest
0 голосов
/ 08 октября 2018

Я пытаюсь построить многопанельный и многострочный график, используя ggplot с групповой эстетикой, а также facet_wrap.Однако geom_smooth завершается неудачно для всех линий на фасетном графике, когда в одной группе слишком мало точек данных.

plot1 <- ggplot(data=df1, 
                aes(x=Year, y=Mean, group=Group2, linetype=Group2, shape=Group2)) +  
  geom_errorbar(aes(ymin=Mean-SE, ymax=Mean+SE), width=0.2) +  
  geom_smooth(method = "gam", se=F, formula = y ~ s(x, k=3), size = 1, colour="black") + 
  geom_point(position=pd, size=2, fill="white") +  
  scale_x_continuous(limits=c(min(df1$Year-0.1), max(df1$Year+0.1)), 
                     breaks=seq(min(df1$Year),max(df1$Year),5)) +  
  facet_wrap(~Group1, scales = "free", ncol=2) +  
  theme_bw() + 
  theme(axis.text.x = element_text(),
        axis.title.x = element_blank(), 
        strip.background = element_blank(), 
        axis.line.x = element_line(colour="black"),
        axis.line.y = element_line(colour="black"), 
        panel.grid.minor = element_blank(), 
        panel.grid.major = element_blank(),
        panel.border = element_blank(), 
        panel.background = element_blank(),
        legend.position="top",
        legend.title = element_blank())
plot(plot1)

для получения участка следующего участка.Это только сводные данные, чтобы было проще.Как будто ошибка останавливает ggplot при вычислении сглаживания рядов в этом конкретном аспекте.

data

Year    Group1      Group2      Mean        SE
2011    Factor A    Factor C    30.62089116 3.672624771
2011    Factor A    Factor D    54.99066324 2.822405771
2011    Factor B    Factor C    30.48859003 3.748388489
2011    Factor B    Factor D    45.70410611 4.284244405
2017    Factor A    Factor C    33.68256601 4.030964172
2017    Factor A    Factor D    53.43496462 4.687042033
2017    Factor B    Factor C    23.08799875 5.17753488
2001    Factor A    Factor C    23.79166667 2.837795432
2001    Factor A    Factor D    23.75925926 3.688185081
2001    Factor B    Factor C    29.05555556 4.08597798
2001    Factor B    Factor D    28.13333333 7.877429079
2008    Factor A    Factor C    23.3        2.383624691
2008    Factor A    Factor D    28.83333333 2.750959429
2008    Factor B    Factor C    34.01666667 5.340999698

и сюжет

plot 1

Очевидно, что данных достаточно, чтобы нарисовать сглаживание для factorCлиния в группе factorB.Любая идея?

Ответы [ 2 ]

0 голосов
/ 08 октября 2018

Очень простой способ справиться с этим - выделить строки, вызывающие проблемы в данных, передаваемых в geom_smooth:

library(tidyverse)

df1 <- data_frame(
    Year = c(2011L, 2011L, 2011L, 2011L, 2017L, 2017L, 2017L, 2001L, 2001L, 2001L, 2001L, 2008L, 2008L, 2008L),
    Group1 = c("Factor A", "Factor A", "Factor B", "Factor B", "Factor A", "Factor A", "Factor B", "Factor A", "Factor A", "Factor B", "Factor B", "Factor A", "Factor A", "Factor B"),
    Group2 = c("Factor C", "Factor D", "Factor C", "Factor D", "Factor C", "Factor D", "Factor C", "Factor C", "Factor D", "Factor C", "Factor D", "Factor C", "Factor D", "Factor C"),
    Mean = c(30.62089116, 54.99066324, 30.48859003, 45.70410611, 33.68256601, 53.43496462, 23.08799875, 23.79166667, 23.75925926, 29.05555556, 28.13333333, 23.3, 28.83333333, 34.01666667),
    SE = c(3.672624771, 2.822405771, 3.748388489, 4.284244405, 4.030964172, 4.687042033, 5.17753488, 2.837795432, 3.688185081, 4.08597798, 7.877429079, 2.383624691, 2.750959429, 5.340999698)
)

ggplot(df1, aes(Year, Mean, color = Group2)) +  
    geom_errorbar(aes(ymin = Mean - SE, ymax = Mean + SE)) +  
    geom_smooth(data = df1 %>% group_by(Group1, Group2) %>% filter(n() > 2),    # subset
                method = "gam", formula = y ~ s(x, k=3)) + 
    geom_point() + 
    facet_wrap(~Group1)

0 голосов
/ 08 октября 2018

Я думаю, что это довольно сложно.После некоторого тестирования и чтения текущего кода GH для StatSmooth я суммировал свои выводы следующим образом:

Наблюдения

  1. geom_smooth() не удалосьчтобы нарисовать все сглаженные линии на панели графика, если любой из групп данных имеет слишком мало наблюдений для method = "gam" AND formula = y ~ s(x, k = 3);
  2. Если график гранен на несколько панелей, затрагиваются только панели с поврежденными группами данных;
  3. Этого не происходит для formula = y ~ x (т. Е. По умолчанию);
  4. Это не происходит для некоторых других методов (например, "lm", "glm") с формулой по умолчанию, но действительно происходит с method = "loess";
  5. Этого не происходит, еслигруппа данных имеет только 1 наблюдение.

Мы можем воспроизвести вышеупомянутое с некоторым упрощенным кодом:

# create sample data
n <- 30
set.seed(567)
df.1 <- data.frame( # there is only 1 observation for group == B
  x = rnorm(n), y = rnorm(n),
  group = c(rep("A", n - 1), rep("B", 1)),
  facet = sample(c("X", "Y"), size = n, replace = TRUE))    
set.seed(567)
df.2 <- data.frame( # there are 2 observations for group == B
  x = rnorm(n), y = rnorm(n),
  group = c(rep("A", n - 2), rep("B", 2)),
  facet = sample(c("X", "Y"), size = n, replace = TRUE))

# create base plot
p <- ggplot(df.2, aes(x = x, y = y, color = group)) + 
  geom_point() + theme_bw()

# problem: no smoothed line at all in the entire plot
p + geom_smooth(method = "gam", formula = y ~ s(x, k = 3))

# problem: no smoothed line in the affected panel
p + facet_wrap(~ facet) + 
  geom_smooth(method = "gam", formula = y ~ s(x, k = 3))

# no problem with default formula: smoothed lines in both facet panels
p + facet_wrap(~ facet) + geom_smooth(method = "gam")

# no problem with lm / glm, but problem with loess
p + facet_wrap(~ facet) + geom_smooth(method = "lm")
p + facet_wrap(~ facet) + geom_smooth(method = "glm")
p + facet_wrap(~ facet) + geom_smooth(method = "loess")

# no problem if there's only one observation (instead of two)
p %+% df.1 + geom_smooth(method = "gam", formula = y ~ s(x, k = 3))
p %+% df.1 + facet_wrap(~ facet) + 
  geom_smooth(method = "gam", formula = y ~ s(x, k = 3))

Пояснения к наблюдениям 1 и 2:

Я полагаю, что проблема заключается в последних двух строках функции StatSmooth compute_group.Первая строка вызывает функцию модели (например, stats::glm, stats::loess, mgcv::gam) для фрейма данных для каждой группы, определенной отображением aes(group = ...), а вторая строка вызывает одну из оболочек вокруг stats::predict(), чтобы получитьсглаженные значения (и доверительный интервал, если применимо) для модели.

model <- do.call(method, c(base.args, method.args))
predictdf(model, xseq, se, level)

Когда параметры method = "gam", formula = y ~ s(x, k = 3) используются для фрейма данных только с 2 наблюдениями, это происходит:

model <- do.call(mgcv::gam,
                 args = list(formula = y ~ s(x, k = 3),
                             data = df.2 %>% filter(group == "B" & facet == "X")))

Ошибка в smooth.construct.tp.smooth.spec (объект, dk $ data, dk $ knots): термин содержит меньше уникальных ковариатных комбинаций, чем указанные максимальные степени свободы

model, объект, определенный для получения результата do.call(...), даже не был создан.Последняя строка кода predictdf(...) выдаст ошибку, потому что model не существует. Без огранки это влияет на все вычисления, выполненные StatSmooth, и geom_smooth() не получает пригодных для использования данных для создания любого geom в его слое. С фасетой вышеуказанные вычисления выполняются отдельно для каждого фасета, поэтому затрагиваются только фасеты с проблемными данными.

Пояснения к наблюдениям 3 и 4:

В дополнение к вышесказанному, если мы не укажем формулу для замены по умолчанию y ~ x, мы получим действительный объект модели из gam / lm / glm, которыйможет быть передано в неэкспортированную функцию ggplot2 predictdf для кадра данных значений прогноза:

model <- do.call(mgcv::gam, # or stats::lm, stats::glm
                 args = list(formula = y ~ x,
                             data = df.2 %>% filter(group == "B" & facet == "X")))

result <- ggplot2:::predictdf(
  model, 
  xseq = seq(-2, 1.5, length.out = 80), # pseudo range of x-axis values
  se = FALSE, level = 0.95) # default SE / level parameters

loess также вернет действительный объект, хотя и с множеством предупреждений.Однако передача его в predictdf приведет к ошибке:

model <- do.call(stats::loess,
                 args = list(formula = y ~ x,
                             data = df.2 %>% filter(group == "B" & facet == "X")))

result <- ggplot2:::predictdf(
  model, 
  xseq = seq(-2, 1.5, length.out = 80), # pseudo range of x-axis values
  se = FALSE, level = 0.95) # default SE / level parameters

Ошибка в объекте predLoess (объект $ y, объект $ x, newx = if (is.null (newdata)) объекта$ x else if (is.data.frame (newdata)) as.matrix (model.frame (delete.response (term (object))),: NA / NaN / Inf в вызове внешней функции (аргумент 5)

Объяснение для наблюдения 5:

StatSmooth Функция compute_group начинается со следующего:

if (length(unique(data$x)) < 2) {
      # Not enough data to perform fit
      return(data.frame())
    }

Другими словами, еслив указанной группе есть только 1 наблюдение, StatSmooth немедленно возвращает пустой фрейм данных и, следовательно, никогда не достигнет последующих частей кода, чтобы выдать ошибку.

Обходной путь:

Определив, где что-то пошло не так, мы можем внести изменения в код compute_group (см. Аннотированные и закомментированные фрагменты):

new.compute_group <- function(
  data, scales, method = "auto", formula = y~x, se = TRUE, n = 80, span = 0.75, 
  fullrange = FALSE, xseq = NULL, level = 0.95, method.args = list(), na.rm = FALSE) {
  if (length(unique(data$x)) < 2) return(data.frame()) 
  if (is.null(data$weight)) data$weight <- 1
  if (is.null(xseq)) {
    if (is.integer(data$x)) {
      if (fullrange) {
        xseq <- scales$x$dimension()
      } else {
        xseq <- sort(unique(data$x))
      }
    } else {
      if (fullrange) {
        range <- scales$x$dimension()
      } else {
        range <- range(data$x, na.rm = TRUE)
      }
      xseq <- seq(range[1], range[2], length.out = n)
    }
  }
  if (identical(method, "loess")) method.args$span <- span 
  if (is.character(method)) method <- match.fun(method)
  base.args <- list(quote(formula), data = quote(data), weights = quote(weight))

  # if modelling fails, return empty data frame
  # model <- do.call(method, c(base.args, method.args))
  model <- try(do.call(method, c(base.args, method.args)))
  if(inherits(model, "try-error")) return(data.frame())

  # if modelling didn't fail, but prediction returns NA,
  # also return empty data frame
  # predictdf(model, xseq, se, level)
  pred <- try(ggplot2:::predictdf(model, xseq, se, level))
  if(inherits(pred, "try-error")) return(data.frame())
  return(pred)
}

Определить новый слой статистики, который использует эту версию:

# same as stat_smooth() except that it uses stat = StatSmooth2, rather 
# than StatSmooth
stat_smooth_local <- function(
  mapping = NULL, data = NULL, geom = "smooth", position = "identity", ...,
  method = "auto", formula = y ~ x, se = TRUE, n = 80, span = 0.75,
  fullrange = FALSE, level = 0.95, method.args = list(), na.rm = FALSE,
  show.legend = NA, inherit.aes = TRUE) {
  layer(
    data = data, mapping = mapping, stat = StatSmooth2,
    geom = geom, position = position, show.legend = show.legend,
    inherit.aes = inherit.aes,
    params = list(
      method = method, formula = formula, se = se, n = n,
      fullrange = fullrange, level = level, na.rm = na.rm,
      method.args = method.args, span = span, ...
    )
  )
}

# inherit from StatSmooth
StatSmooth2 <- ggproto(
  "StatSmooth2", ggplot2::StatSmooth,
  compute_group = new.compute_group
)

Результат:

Мы можем выполнить те же случаи, что и раньше, заменив geom_smooth() на stat_smooth_local(), и убедитесь, чтосглаженные слои geom видны в каждом случае (обратите внимание, что некоторые из них по-прежнему будут вызывать сообщения об ошибках):

# problem resolved: smoothed line for applicable group in the entire plot
p + stat_smooth_local(method = "gam", formula = y ~ s(x, k = 3))

# problem resolved: smoothed line for applicable group in the affected panel
p + facet_wrap(~ facet) + 
  stat_smooth_local(method = "gam", formula = y ~ s(x, k = 3))

# still no problem with default formula
p + facet_wrap(~ facet) + stat_smooth_local(method = "gam")

# still no problem with lm / glm; problem resolved for loess
p + facet_wrap(~ facet) + stat_smooth_local(method = "lm")
p + facet_wrap(~ facet) + stat_smooth_local(method = "glm")
p + facet_grid(~ facet) + stat_smooth_local(method = "loess")

# still no problem if there's only one observation (instead of two)
p %+% df.1 + stat_smooth_local(method = "gam", formula = y ~ s(x, k = 3))
p %+% df.1 + facet_wrap(~ facet) + 
  stat_smooth_local(method = "gam", formula = y ~ s(x, k = 3))

# showing one pair of contrasts here
cowplot::plot_grid(
  p + facet_wrap(~ facet) + ggtitle("Before") +
    geom_smooth(method = "gam", formula = y ~ s(x, k = 3)),
  p + facet_wrap(~ facet) + ggtitle("After") +
    stat_smooth_local(method = "gam", formula = y ~ s(x, k = 3)),
  nrow = 2
)

plot

...