как добавить слои в ggplot с помощью цикла for

#r #ggplot2

Вопрос:

Я хотел бы отобразить каждый столбец фрейма данных на отдельном слое в ggplot2. Построение графика слой за слоем хорошо работает:

 df<-data.frame(x1=c(1:5),y1=c(2.0,5.4,7.1,4.6,5.0),y2=c(0.4,9.4,2.9,5.4,1.1),y3=c(2.4,6.6,8.1,5.6,6.3))

ggplot(data=df,aes(df[,1])) geom_line(aes(y=df[,2])) geom_line(aes(y=df[,3]))
 

Есть ли способ отобразить все доступные столбцы в единицах с помощью одной функции?

Я пытался сделать это таким образом, но это не работает:

     plotAllLayers<-function(df){
    p<-ggplot(data=df,aes(df[,1]))
    for(i in seq(2:ncol(df))){ 
        p<-p geom_line(aes(y=df[,i]))
        }
        return(p)
    }

plotAllLayers(df)
 

Ответ №1:

Одним из подходов было бы изменить формат фрейма данных с широкоформатного на длинноформатный с помощью функции melt() из библиотеки reshape2 . В новом фрейме данных у вас будут x1 значения, variable которые определяют, из какого столбца пришли данные, и value которые содержат все исходные значения y.

Теперь вы можете построить все данные с помощью одного ggplot() и geom_line() вызвать и использовать variable , например, отдельный цвет для каждой строки.

  library(reshape2)
 df.long<-melt(df,id.vars="x1")
 head(df.long)
  x1 variable value
1  1       y1   2.0
2  2       y1   5.4
3  3       y1   7.1
4  4       y1   4.6
5  5       y1   5.0
6  1       y2   0.4
 ggplot(df.long,aes(x1,value,color=variable)) geom_line()
 

введите описание изображения здесь

Если вы действительно хотите использовать цикл for () (не лучший способ), то вам следует использовать names(df)[-1] вместо seq() . Это создаст вектор имен столбцов (кроме первого столбца). Затем внутри geom_line() используйте aes_string(y=i) , чтобы выбрать столбец по их имени.

 plotAllLayers<-function(df){
  p<-ggplot(data=df,aes(df[,1]))
  for(i in names(df)[-1]){ 
    p<-p geom_line(aes_string(y=i))
  }
  return(p)
}

plotAllLayers(df)
 

введите описание изображения здесь

Комментарии:

1. Спасибо, это ответ на мой вопрос. Это действительно помогает. изменение формы 2 очень полезно. Я думаю, что мне нужно привыкнуть к длинному формату.

Ответ №2:

Я попробовал метод melt на большом грязном наборе данных и пожелал более быстрого и чистого метода. Этот цикл for использует eval() для построения желаемого графика.

 fields <- names(df_normal) # index, var1, var2, var3, ...

p <- ggplot( aes(x=index), data = df_normal)
for (i in 2:length(fields)) { 
  loop_input = paste("geom_smooth(aes(y=",fields[i],",color='",fields[i],"'))", sep="")
  p <- p   eval(parse(text=loop_input))  
}
p <- p   guides( color = guide_legend(title = "",) )
p
 

Это работало намного быстрее, чем большой расплавленный набор данных, когда я тестировал.

Я также попробовал цикл for с помощью метода aes_string(y=поля[i], цвет=поля[i]), но не смог различить цвета.

Комментарии:

1. Единственный ответ на этот вопрос, который я видел, который действительно работает и действительно работает. Конечно, многие считают «оценку» смертным грехом, но она существует не просто так 😛

2. Спасибо!! В качестве примечания для последующих посетителей я обнаружил, что присвоение данных переменной с помощью итератора (например subset_of_data <- some_data_frame[i] ), похоже, не работает. Вместо того , чтобы добавлять все больше и больше компонентов p , только последний, кажется, вступает в силу.

Ответ №3:

Для ситуации с операцией, я думаю pivot_longer , это лучше всего. Но сегодня у меня была ситуация, которая, казалось, не поддавалась повороту, поэтому я использовал следующий код для программного создания слоев. Мне не нужно было использовать eval() .

 data_tibble <- tibble(my_var = c(650, 1040, 1060, 1150, 1180, 1220, 1280, 1430, 1440, 1440, 1470, 1470, 1480, 1490, 1520, 1550, 1560, 1560, 1600, 1600, 1610, 1630, 1660, 1740, 1780, 1800, 1810, 1820, 1830, 1870, 1910, 1910, 1930, 1940, 1940, 1940, 1980, 1990, 2000, 2060, 2080, 2080, 2090, 2100, 2120, 2140, 2160, 2240, 2260, 2320, 2430, 2440, 2540, 2550, 2560, 2570, 2610, 2660, 2680, 2700, 2700, 2720, 2730, 2790, 2820, 2880, 2910, 2970, 2970, 3030, 3050, 3060, 3080, 3120, 3160, 3200, 3280, 3290, 3310, 3320, 3340, 3350, 3400, 3430, 3540, 3550, 3580, 3580, 3620, 3640, 3650, 3710, 3820, 3820, 3870, 3980, 4060, 4070, 4160, 4170, 4170, 4220, 4300, 4320, 4350, 4390, 4430, 4450, 4500, 4650, 4650, 5080, 5160, 5160, 5460, 5490, 5670, 5680, 5760, 5960, 5980, 6060, 6120, 6190, 6480, 6760, 7750, 8390, 9560))

# This is a normal histogram
plot <- data_tibble %>%
  ggplot()  
  geom_histogram(aes(x=my_var, y = ..density..))

# We prepare layers to add
stat_layers <- tibble(distribution = c("lognormal", "gamma", "normal"),
                     fun = c(dlnorm, dgamma, dnorm),
                     colour = c("red", "green", "yellow")) %>% 
  mutate(args = map(distribution, MASS::fitdistr, x=data_tibble$my_var)) %>% 
  mutate(args = map(args, ~as.list(.$estimate))) %>% 
  select(-distribution) %>% 
  pmap(stat_function)

# Final Plot
plot   stat_layers
 

Идея состоит в том, что вы организуете tibble с аргументами, которые вы хотите подключить к функции geom/stat. Каждая строка должна соответствовать слою, который вы хотите добавить в ggplot. Затем используйте pmap . Это создает список слоев, которые вы можете просто добавить к своему графику.

Ответ №4:

Изменение формы ваших данных, чтобы вам не нужен был цикл, — лучший вариант. В противном случае в более новых версиях ggplot вы можете использовать .data местоимение внутри aes() . Вы можете сделать

 plotAllLayers<-function(df){
    p <- ggplot(data=df, aes(df[,1]))
    for(i in names(df)[2:ncol(df)]){ 
        p <- p   geom_line(aes(y=.data[[i]]))
    }
    return(p)
}
plotAllLayers(df)
 

Мы используем .data местоимение, чтобы получить данные, переданные ggplot объекту, и перебираем имена столбцов, потому .data что по какой-то причине им не нравятся индексы.