Замыкания как решение идиомы слияния данных - PullRequest
6 голосов
/ 17 октября 2011

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

У меня есть следующие части для работы:

  • Набор регулярных выражений, предназначенных для очистки имен состояний, размещенных в функции
  • data.frame с именами состояний (стандартизированной формы, которую создает вышеуказанная функция) и кодами идентификаторов состояний, чтобы связать их («карта слияния»)

Идея состоит в том, чтобы, учитывая некоторые data.frame с неаккуратными названиями штатов (прописная буква "Вашингтон, округ Колумбия", "Вашингтон, округ Колумбия", "Округ Колумбия" и т. Д.), Иметь одну функцию возврата тот же файл data.frame с удаленным столбцом имени состояния и оставшимися только кодами идентификаторов состояний. Тогда последующие слияния могут происходить последовательно.

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

Вопрос 1. Это разумная идея?

Вопрос 2: Если так, как мне это сделать в R?

Вот глупая простая функция очистки имен состояний, которая работает с данными примера:

cleanStateNames <- function(x) {
  x <- tolower(x)
  x[grepl("columbia",x)] <- "DC"
  x
}

Вот некоторые примеры данных, на которых будет выполняться возможная функция:

dat <- structure(list(state = c("Alabama", "Alaska", "Arizona", "Arkansas", 
"California", "Colorado", "Connecticut", "Delaware", "District of Columbia", 
"Florida"), pop08 = structure(c(29L, 44L, 40L, 18L, 25L, 30L, 
22L, 48L, 36L, 13L), .Label = c("1,050,788", "1,288,198", "1,315,809", 
"1,316,456", "1,523,816", "1,783,432", "1,814,468", "1,984,356", 
"10,003,422", "11,485,910", "12,448,279", "12,901,563", "18,328,340", 
"19,490,297", "2,600,167", "2,736,424", "2,802,134", "2,855,390", 
"2,938,618", "24,326,974", "3,002,555", "3,501,252", "3,642,361", 
"3,790,060", "36,756,666", "4,269,245", "4,410,796", "4,479,800", 
"4,661,900", "4,939,456", "5,220,393", "5,627,967", "5,633,597", 
"5,911,605", "532,668", "591,833", "6,214,888", "6,376,792", 
"6,497,967", "6,500,180", "6,549,224", "621,270", "641,481", 
"686,293", "7,769,089", "8,682,661", "804,194", "873,092", "9,222,414", 
"9,685,744", "967,440"), class = "factor")), .Names = c("state", 
"pop08"), row.names = c(NA, 10L), class = "data.frame")

И пример карты слияния (фактическая карта связывает коды FIPS с состояниями, поэтому ее нельзя сгенерировать тривиально):

merge_map <- data.frame(state=dat$state, id=seq(10) )

РЕДАКТИРОВАТЬ Построение ответа Crippledlambda ниже, вот попытка функции:

prepForMerge <- local({
  merge_map <- structure(list(state = c("alabama", "alaska", "arizona", "arkansas",  "california", "colorado", "connecticut", "delaware", "DC", "florida" ), id = 1:10), .Names = c("state", "id"), row.names = c(NA, -10L ), class = "data.frame")
  list(
    replace_merge_map=function(new_merge_map) {
      merge_map <<- new_merge_map
    },
    show_merge_map=function() {
      merge_map
    },
    return_prepped_data.frame=function(dat) {
      dat$state <- cleanStateNames(dat$state)
      dat <- merge(dat,merge_map)
      dat <- subset(dat,select=c(-state))
      dat
    }
  )
})

> prepForMerge$return_prepped_data.frame(dat)
        pop08 id
1   4,661,900  1
2     686,293  2
3   6,500,180  3
4   2,855,390  4
5  36,756,666  5
6   4,939,456  6
7   3,501,252  7
8     591,833  9
9     873,092  8
10 18,328,340 10

Осталось две проблемы, прежде чем я решу этот вопрос решенным:

  1. Звонить prepForMerge$return_prepped_data.frame(dat) каждый раз больно. Есть ли способ иметь функцию по умолчанию, чтобы я мог просто вызвать prepForMerge (dat)? Я догадываюсь, не учитывая, как это реализовано, но, возможно, есть по крайней мере соглашение для fxn по умолчанию ....

  2. Как избежать смешивания данных и кода в определении merge_map? В идеале я бы очистил merge_map в другом месте, а затем просто взял бы его внутри замыкания и сохранил его.

1 Ответ

4 голосов
/ 17 октября 2011

Возможно, я упускаю суть вашего вопроса, но это единственный способ, которым вы можете использовать замыкание:

> replaceStateNames <- local({
+   statenames <- c("Alabama", "Alaska", "Arizona", "Arkansas", 
+                   "California", "Colorado", "Connecticut", "Delaware",
+                   "District of Columbia", "Florida")
+   function(patt,newtext) {
+     statenames <- tolower(statenames)
+     statenames[grepl(patt,statenames)] <- newtext
+     statenames
+   }
+ })
> 
> replaceStateNames("columbia","DC")
 [1] "alabama"     "alaska"      "arizona"     "arkansas"    "california" 
 [6] "colorado"    "connecticut" "delaware"    "DC"          "florida"    
> replaceStateNames("alaska","palincountry")
 [1] "alabama"              "palincountry"         "arizona"             
 [4] "arkansas"             "california"           "colorado"            
 [7] "connecticut"          "delaware"             "district of columbia"
[10] "florida"             
> replaceStateNames("florida","jebbushland")
 [1] "alabama"              "alaska"               "arizona"             
 [4] "arkansas"             "california"           "colorado"            
 [7] "connecticut"          "delaware"             "district of columbia"
[10] "jebbushland"    
> 

Но для обобщения вы можете заменить statenames на ваше определение фрейма данных.и вернуть функцию (или список функций), которая использует этот фрейм данных, не передавая его в качестве аргумента в вызов функции.Пример (но обратите внимание, что я использовал аргумент ignore.case=TRUE в grepl):

> replaceStateNames <- local({
+   statenames <- c("Alabama", "Alaska", "Arizona", "Arkansas", 
+                   "California", "Colorado", "Connecticut", "Delaware",
+                   "District of Columbia", "Florida")
+   list(justreturn=function(patt,newtext) {
+     statenames[grepl(patt,statenames,ignore.case=TRUE)] <- newtext
+     statenames
+   },reassign=function(patt,newtext) {
+     statenames <<- replace(statenames,grepl(patt,statenames,ignore.case=TRUE),newtext)
+     statenames
+   })
+ })

Как и в первом примере:

> replaceStateNames$justreturn("columbia","DC")
 [1] "Alabama"     "Alaska"      "Arizona"     "Arkansas"    "California" 
 [6] "Colorado"    "Connecticut" "Delaware"    "DC"          "Florida"    

Просто возвращает значение в лексической областииз statenames, чтобы проверить, что исходные значения не изменены:

> replaceStateNames$justreturn("shouldnotmatch","anythinghere")
 [1] "Alabama"              "Alaska"               "Arizona"             
 [4] "Arkansas"             "California"           "Colorado"            
 [7] "Connecticut"          "Delaware"             "District of Columbia"
[10] "Florida"             

Сделайте то же самое, но сделайте изменение "постоянным":

> replaceStateNames$reassign("columbia","DC")
 [1] "Alabama"     "Alaska"      "Arizona"     "Arkansas"    "California" 
 [6] "Colorado"    "Connecticut" "Delaware"    "DC"          "Florida"    

И обратите внимание, что значениеstatenames привязка к этим функциям изменилась.

> replaceStateNames$justreturn("shouldnotmatch","anythinghere")
 [1] "Alabama"     "Alaska"      "Arizona"     "Arkansas"    "California" 
 [6] "Colorado"    "Connecticut" "Delaware"    "DC"          "Florida"    

В любом случае вы можете заменить statenames фреймом данных, а эти простые функции - «картой слияния» или любым другим отображением по вашему желанию..

Редактировать

Говоря о "слиянии", это то, что вы ищете?Реализация первого ?merge примера с использованием замыкания:

> authors <- data.frame(surname = I(c("Tukey", "Venables", "Tierney", "Ripley", "McNeil")),
+                       nationality = c("US", "Australia", "US", "UK", "Australia"),
+                       deceased = c("yes", rep("no", 4)))
> books <- data.frame(name = I(c("Tukey", "Venables", "Tierney",
+                       "Ripley", "Ripley", "McNeil", "R Core")),
+                     title = c("Exploratory Data Analysis",
+                       "Modern Applied Statistics ...",
+                       "LISP-STAT",
+                       "Spatial Statistics", "Stochastic Simulation",
+                       "Interactive Data Analysis",
+                       "An Introduction to R"),
+                     other.author = c(NA, "Ripley", NA, NA, NA, NA,
+                       "Venables & Smith"))
> 
> mergewithauthors <- with(list(authors=authors),function(books) 
+   merge(authors, books, by.x = "surname", by.y = "name"))
> 
> mergewithauthors(books)
   surname nationality deceased                         title other.author
1   McNeil   Australia       no     Interactive Data Analysis         <NA>
2   Ripley          UK       no            Spatial Statistics         <NA>
3   Ripley          UK       no         Stochastic Simulation         <NA>
4  Tierney          US       no                     LISP-STAT         <NA>
5    Tukey          US      yes     Exploratory Data Analysis         <NA>
6 Venables   Australia       no Modern Applied Statistics ...       Ripley

Редактирование 2

Чтобы прочитать файл в объект, который будет лексически связан, вы можете либосделать

fn <- local({
  data <- read.csv("filename.csv")
  function(...) {
    ...
  }
})

или

fn <- with(list(data=read.csv("filename.csv")),
     function(...) {
       ...
     }
   })

или

fn <- with(local(data <- read.csv("filename.csv")),
     function(...) {
       ...
     }
   })

и так далее.(Я предполагаю, что функция (...) будет иметь отношение к вашему "merge_map").Вы также можете использовать evalq вместо local.Чтобы «ввести» объекты, находящиеся в глобальном пространстве (или в окружающей среде), вы можете просто сделать следующее

globalobj <- value      ## could be from read.csv()
fn <- local({
  localobj <- globalobj ## if globalobj is not locally defined, 
                        ## R will look in enclosing environment
                        ## in this case, the globalenv()
  function(...) {
    ...
  }
})

, тогда модификация globalobj позже не изменит localobj присоединенного к функции (так какпочти (?) все в R следует семантике передачи по значению).Вы также можете использовать with вместо local, как показано в примерах выше.

...