2015-12-09 12 views
5

我經常會遇到太多分類變量無法令人滿意地繪製到一個圖上的數據。當出現這種情況時,我會寫一些東西來循環變量並保存特定於該變量的幾個圖。當您有一個額外的變量時,循環在R中創建許多圖塊

通過以下例子可以說明這個過程:

library(tidyr) 
library(dplyr) 
library(ggplot2) 

mtcars <- add_rownames(mtcars, "car") 

param<-unique(mtcars$cyl) 
for (i in param) 
{ 
mcplt <- mtcars %>% filter(cyl==i) %>% ggplot(aes(x=mpg, y=hp)) + 
    geom_point() + 
    facet_wrap(~car) + 
    ggtitle(paste("Cylinder Type: ",i,sep="")) 
    ggsave(mcplt, file=paste("Type",i,".jpeg",sep="")) 
} 

每次,我在網上看到,雖然循環引用,每個人似乎都在表明,循環通常不會在R.一個很好的策略。如果是這樣的話,任何人都可以推薦一個更好的方式來達到與上面相同的結果嗎?我會特別感興趣的東西,因爲SOOOO速度較慢。但也許解決方案是,這是最好的解決方案。我只是好奇,如果有人能改善這一點。

在此先感謝。

+1

我覺得你的循環是偉大的,因爲寫的。循環在R中得到很多非常不公平的壞消息。 – bdemarest

+0

同意循環是可以的。代碼加載tidyr但不使用它;可以改進循環縮進,'paste(...,sep =「」)'最好寫成'paste0(...)'或使用'sprintf'。 –

+1

在R中經常不鼓勵循環,因爲很多常用的循環式函數都是作爲基本R函數構建的,應該這樣使用,但是由於那些不在低級語言中,具有其他語言背景的程序員可能傾向於即使在簡單情況下也可以進行循環,如計算矢量/數組元素的總和。當然,這可以擴展到更復雜的情況。但我同意使用'for'循環來生成幾個(圖形)不同變量的相同圖,這是非常好的用法,就像我自己做的那樣。 – Molx

回答

4

這是一個關於R的主題,請參閱SO帖子herehereAnswers to this question強調*apply()替代for()提高清晰度,使並行化更容易,並在某些情況下加快問題。但是,大概你的真正的的問題是'我該如何做得更快'',因爲它花的時間太長了,你不開心。在你的循環內,你正在做3個不同的任務。

  1. 打破了使用filter()
  2. 做圖數據幀的一大塊。
  3. 將圖表保存爲jpeg。

有三種方法可以完成這三個步驟,所以讓我們嘗試並評估所有這些步驟。我將使用ggplot2中的鑽石數據,因爲它比汽車數據大。我希望以這種方式注意方法之間的性能差異。我從this chapter of Hadley Wickham's book on measuring performance瞭解到很多。

爲了能夠使用分析功能,我將下面的代碼塊放入函數中,並將其保存在名爲for_solution.r的單獨R文件中。

f <- function(){ 
    param <- unique(diamonds$cut) 
    for (i in param){ 
    mcplt <- diamonds %>% filter(cut==i) %>% ggplot(aes(x=carat, y=price)) + 
     geom_point() + 
     facet_wrap(~color) + 
     ggtitle(paste("Cut: ",i,sep="")) 
    ggsave(mcplt, file=paste("Cut",i,".jpeg",sep="")) 
    } 
} 

,然後我做的:

library(dplyr) 
library(ggplot2) 
source("for_solution.r",keep.source=TRUE) 
Rprof(line=TRUE) 
f() 
Rprof(NULL) 
summaryRprof(lines="show") 

審查其輸出我看到的代碼塊花費97.25%的時間剛剛保存的文件。檢查ggsave()的來源,我可以看到該功能正在進行大量的防禦性編程,以識別輸出的類型,然後打開圖形設備,打印並關閉設備。所以我想知道如果手動做這個步驟會有幫助。我也將利用這樣一個事實,即一個jpeg設備將自動爲每個頁面生成新文件,以便只打開和關閉一次設備。

f1 <- function(){ 
    param <- unique(diamonds$cut) 
    jpeg("cut%03d.jpg",width=par("din")[1],height=par("din")[2],units="in",res=300) # open the jpeg device, change defaults to match ggsave() 
    for (i in param){ 
    mcplt <- diamonds %>% filter(cut==i) %>% ggplot(aes(x=carat, y=price)) + 
     geom_point() + 
     facet_wrap(~color) + 
     ggtitle(paste("Cut: ",i,sep="")) 
    print(mcplt) 
    } 
    dev.off() 
} 

現在又來剖析

Rprof(line=TRUE) 
f1() 
Rprof(NULL) 
summaryRprof(lines="show") 

f1()仍然花費大部分的時間在print(mcplt),並且它比以前稍快(1.96秒相比2.18秒)。加快速度的一種可能方式是使用更小的設備(分辨率更低或圖像更小);當我使用jpeg()的默認值時,差異更大,更快了25%。我也嘗試將設備更改爲png(),但這並沒有什麼不同。

基於性能分析,我不認爲這會有所幫助,但爲了完整起見,我將嘗試取消for循環,並在012p內運行dplyr中的所有內容。我發現this questionthis one在這裏很有幫助。

jpeg("cut%03d.jpg",width=par("din")[1],height=par("din")[2],units="in",res=300) # open the jpeg device, change defaults to match ggsave() 
plots = diamonds %>% group_by(cut) %>% 
    do({plot=ggplot(aes(x=carat, y=price),data=.) + 
     geom_point() + 
     facet_wrap(~color) + 
     ggtitle(paste("Cut: ",.$cut,sep="")) 
    print(plot)}) 

dev.off() 

運行的代碼給

Error: Results are not data frames at positions: 1, 2, 3

,但它似乎工作。我相信do()返回時會出現錯誤,因爲print()方法沒有返回data.frame。分析它似乎表明它運行速度更快,總體爲1.78秒。但我不喜歡解決方案產生錯誤,即使它們不會造成問題。

我要停在這裏,但我已經學會了在哪裏集中了極大關注。其他的事情,試圖將包括:

  1. 使用parallel或類似於一個獨立的進程中運行數據幀的每個塊的東西。如果問題是保存文件,我不確定這會有幫助,但是如果渲染圖像是由CPU完成的話,我想。
  2. 嘗試使用data.table而不是dplyr,但再一次,它的打印速度很慢。
  3. 嘗試使用基礎圖形和點陣圖形,而不是使用ggplot2。我不知道相對速度,但它可能會有所不同。
  4. 購買更快的硬盤!我只是將家用電腦上的f()速度與普通硬盤驅動器的速度比較,以便我的工作機器使用SSD - 速度比上述時間慢3倍。
+0

哇。這非常有幫助。這次真是萬分感謝。 – boshek

+0

那麼標記它是正確的呢? –