2017-09-06 120 views
2

我正在嘗試將一個Tibble轉換爲函數調用的參數列表。我這樣做的原因是因爲我想創建一個簡單的文件說明Tibble,用於讀取具有不同列的多個固定寬度文件。這樣我只需要使用pull和select來指定文件中的哪些列,然後我就可以自動地加載和解析文件。但是,我遇到了使用cols對象指定列格式的問題。將Tibble轉換爲參數列表

對於這個例子讓我們假設我有格式的Tibble:

> (filespec <- tibble(ID = c("Title", "Date", "ATTR"), Length = c(23, 8, 6), Type = c("col_character()", "col_date()", "col_factor(levels=c(123456,654321)"))) 
# A tibble: 3 x 3 
    ID Length        Type 
    <chr> <dbl>        <chr> 
1 Title  23     col_character() 
2 Date  8       col_date() 
3 ATTR  6 col_factor(levels=c(123456,654321) 

我想用格式的COLS對象落得:

> (cols(Title = col_character(), Date = col_date(), ATTR=col_factor(levels=c(123456,654321)))) 
cols(
    Title = col_character(), 
    Date = col_date(format = ""), 
    ATTR = col_factor(levels = c(123456, 654321), ordered = FALSE) 
) 

從其他的問題,我已閱讀我知道這可以通過do.call完成。但我無法弄清楚如何以自動方式將列ID和類型轉換爲cols對象。下面是我試過的例子...

> do.call(cols, select(filespec,ID, Type)) 
Error in switch(x, `_` = , `-` = col_skip(), `?` = col_guess(), c = col_character(), : 
    EXPR must be a length 1 vector 

我假設的選擇需要與執行行參數映射另一個函數來包裝,這是怎麼完成的?

+1

你可能能夠用do.call做到這一點,但是你的代碼並不能遠程做你想做的事 - 你需要先理解do.call在你之前實際做了些什麼可以使用它。 –

+0

我是R新手,所以這都是一種學習體驗。我想我明白do.call做了什麼,它調用一個函數,其他參數作爲參數。根據我對下面答案的評論,我認爲在這裏逃避的是如何以自動化的方式創建一個命名列表。我不想手動輸入所有的field = type參數,我把它們分成兩列,我只想讓R爲我創建指定的列表。 – RandomString

+0

是的,你實際上在你的問題描述中發現。從你的問題來看,你似乎沒有理解這一點。但是這部分問題實際上可以通過'setNames'方便地解決。另一個更大的問題是你的參數是字符串,而不是代碼。因此,你首先需要對它們進行評估,儘管這是可能的(通過解析/評估),但它很混亂,可能不是一個好主意(以及你的情況)。喬蘭的方法是優越的。 –

回答

0

TL;博士:有很多事情這使得這看起來更復雜。但是,這是可行的,並且一旦個別部分被理解,所得到的代碼(在最後提供)並不複雜。

正如評論中所討論的,我基本上更喜歡喬蘭的方法。事實上,每當你發現自己在字符串中存儲代碼表達式時,這應該引起警鐘:它是一種反模式,稱爲stringly typed code(與strongly typed code相反,而與之相反)。不幸的是R相當充滿了字符串類型的代碼。

也就是說,你的用例(基於文件的配置)本身就是一個好主意。我會考慮以不同於R代碼片段的格式存儲信息。但是,它的確行得通。所以讓我們來探討爲什麼你的代碼不起作用。

第一個問題是這樣的:你將一個tibble傳遞給do.call。 Tibbles是列的列表,所以do.call允許這樣做。但是,您的內部呼叫轉換爲相當於:

cols(
    ID = c("Title", "Date", "ATTR"), 
    Type = c("col_character()", "col_date()", "col_factor(levels=c(123456,654321))") 
) 

- 但這不是我們想要的代碼!

這裏我們需要解決兩件事情:

  1. 我們需要使用Type列作爲參數ID列作爲參數。我們可以通過創建一個名稱爲ID,值爲Type的新列表來做到這一點:setNames(Type, ID)
  2. cols不知道如何處理字符串參數。它需要列規格 - 類型爲Collector的對象。

    換句話說,不管你寫"col_date()"還是col_date(),這都是巨大的差異。

爲了解決這個問題,我們需要做一些相當複雜的:我們的東東解析Type柱爲R代碼,我們需要評估所產生的解析表達式。 R提供了兩個方便的功能(分別爲parseeval)來完成此操作。但不要讓兩個簡單功能的存在欺騙你:這是一個非常複雜的操作。 R本質上需要在代碼片段上運行完整的解析器和解釋器。如果代碼不符合你的期望,它會變得更加複雜。例如,文本可能包含代碼unlink('/', recursive = TRUE)而不是col_date()。 R然後會高興地擦除你的硬盤。

這只是其中一個的原因爲什麼parse/eval是複雜的,通常可以避免。其他原因包括:如果代碼中存在解析錯誤(實際上,您的代碼包含缺少的右括號......),會發生什麼情況?

但是,我們走了。現在,我們擁有所有的拼在一起,我們就可以比較容易地加入他們的行列:

filespec %>% 
    mutate(Parsed = lapply(Type, function (x) parse(text = x, encoding = 'UTF-8'))) %>% 
    mutate(ColSpec = lapply(Parsed, eval)) %>% 
    with(setNames(ColSpec, ID)) %>% 
    do.call(cols, .) 

一塊執行這個代碼塊,看看它做什麼,並說服自己,它的正常工作。

+1

setNames /與部分正是我所需要的。我知道eval問題,但將在稍後修復它,很可能是通過類型字符串 - > S3對象的簡單映射。 – RandomString

1

我可能不同的方法處理這一點,並且該文件規格存儲在一個簡單的列表:

library(purrr) 
library(readr) 
filespec <- list(Title = list(Length = 23, 
           Type = col_character()), 
       Date = list(Length = 8, 
          Type = col_date()), 
       ATTR = list(Length = 6, 
          Type = col_factor(levels = 123456,654321))) 

a <- at_depth(.x = filespec,.depth = 1,.f = "Type") 
> invoke(.f = cols,.x = a) 

cols(
    Title = col_character(), 
    Date = col_date(format = ""), 
    ATTR = col_factor(levels = 123456, ordered = 654321, include_na = FALSE) 
) 

,或者

> invoke(.f = cols,.x = a[c('Title','ATTR')]) 
cols(
    Title = col_character(), 
    ATTR = col_factor(levels = 123456, ordered = 654321, include_na = FALSE) 
) 
+0

我喜歡這個解決方案,它的工作原理!我使用Tibble的主要原因是最終我可能有50-60列,並且在源文件中維護該列表可能很煩人,所以我希望能夠通過csv讀入。是否有一種簡單的方法可以將我需要的兩列從t take中移出並將它們變成一個列表?我是R新手,我認爲用自動化方式創建一個命名列表的方法正在逃避我。 – RandomString