Оценка динамических переменных в dplyr или векторизованном методе - PullRequest
0 голосов
/ 13 октября 2018

Обычно я справлялся бы с такой проблемой, выполняя цикл (вероятно, не самое лучшее решение до сих пор), но я работаю с очень большим набором данных (7,8 миллиона наблюдений), и я пытался программировать его более эффективно.Вот очень небольшое подмножество моего набора данных:

df = data.frame(STATE = c("PA", "PA", "MD","MD", "MO", "MO"), 
            DIVISION = c("Middle_Atlantic", "Middle_Atlantic","South_Atlantic","South_Atlantic","West_North_Central","West_North_Central"), 
            Middle_Atlantic_NSA = c(117.77, 119.43, 119.43, 120.72, 119.11, 117.77), 
            Middle_Atlantic_SA = c(118.45,  119.65, 119.65, 120.73, 119,    118.45), 
            South_Atlantic_NSA = c(134.45,  135.2,  135.2,  136.69, 134.07, 134.45), 
            South_Atlantic_SA = c(134.25,   134.83, 134.83, 135.97, 133.86, 134.25), 
            West_North_Central_NSA=c(152.24,    153.61, 153.61, 155.19, 151.08, 152.24), 
            West_North_Central_SA=c(152.77, 153.19, 153.19, 154.44, 151.63, 152.77), 
            DIV_HPI_NSA = c(117.77, 119.43, 135.2,  136.69, 151.08, 152.24), 
            DIV_HPI_SA = c(118.45,  119.65, 134.83, 135.97, 151.63, 152.77))

Я включил желаемый вывод для переменных «DIV_HPI_NSA» и «DIV_HPI_SA».Я пытаюсь выполнить поиск значения в «DIVISION» (например, «Middle_Atlantic»), прикрепив к нему суффикс «_NDA», и вернуть соответствующее значение этой переменной (в данном случае «Middle_Atlantic») в новую переменную ».DIV_HPI_NSA».Я делаю то же самое для переменной "DIV_HPI_SA".В настоящее время я пытаюсь использовать либо функцию get (), либо метод eval (parse (text = "text_here")) для оценки строк как имен столбцов и получения правильных значений, однако они не работают так, как мне нужно.В идеале я бы предпочел решение dplyr, поскольку оно обрабатывалось относительно быстро, в отличие от циклов.Я не уверен, почему это не работает в dplyr, и хотел бы понять, почему и как я мог выполнить это успешно.Вот скриншот согласованного с цветом желаемого результата.

enter image description here

Вот мой текущий код:

comb.df = df %>%
mutate(DIV_HPI_NSA = get(paste0(DIVISION,"_NSA")), 
       DIV_HPI_SA = eval(parse(text = (paste0(DIVISION,"_SA"))))) 

Вот какЯ бы сделал это через цикл - который дает правильный результат, но это занимает смехотворное количество времени:

for(i in 1:dim(comb.df)[1]){
    comb.df$DIV_HPI_NSA[i] = comb.df[i, paste0(comb.df$DIVISION[i],"_NSA")]
    comb.df$DIV_HPI_SA[i] = comb.df[i, paste0(comb.df$DIVISION[i],"_SA")]
}

Мой текущий вывод (т.е. DIV_HPI_NSA) продолжает обеспечивать вывод столбца, который соответствует первому оцениваемому элементув колонке "ОТДЕЛЕНИЕ".Например, метод dplyr для «DIV_HPI_NSA» возвращает только значения из столбца «Middle_Atlantic_NSA», поскольку это первый элемент в «DIVISION».Eval () также имеет ту же проблему и не генерирует правильный вывод строк.

Есть ли лучший / более быстрый метод, чем dplyr, и / или как я могу исправить свой код dplyr, чтобы он работал правильно?

Пожалуйста, дайте мне знать, если вам может понадобиться дополнительная информация.

Заранее спасибо!

1 Ответ

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

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

Вот небольшой тест только с "_NSA", но, очевидно, вы можете сделать то же самое с "_SA" позже.

#your base function in a for loop
x1 = function(db){
  for(i in 1:dim(db)[1]){
    db$DIV_HPI_NSA[i] = db[i, paste0(db$DIVISION[i],"_NSA")]
    db$DIV_HPI_SA[i] = db[i, paste0(db$DIVISION[i],"_SA")]
  }
  db}

#the very same function using 'apply', which is supposed to be much faster than base loop
x2= function(db){
  db %>% apply(1, function(x){
    x["DIV_HPI_NSA2"] = x[paste0(x["DIVISION"],"_NSA")]
    x["DIV_HPI_SA2"] = x[paste0(x["DIVISION"],"_SA")]
    x
  }) %>% t %>% as.data.frame
  }

#if DIVISION have few values, you can use 'dplyr::case_when' this way
x3= function(db){
  db %>% mutate(output2 = case_when(
    DIVISION=="Middle_Atlantic" ~ Middle_Atlantic_NSA,
    DIVISION=="South_Atlantic" ~ South_Atlantic_NSA,
    DIVISION=="West_North_Central" ~ West_North_Central_NSA
  ))
}

#but if DIVISION can take a lot of values, you may have to rlang the function a bit
x4= function(db){
  db = db %>% mutate(output2 = -999) #start with dummy value
  xx=data.frame(A=dff$DIVISION, B=paste0(dff$DIVISION,"_NSA"), stringsAsFactors = F) %>% 
    unique %>% 
    split(seq(nrow(.))) #turns xx into a list of its rows
  for(i in xx){
    db = db %>% mutate(output2 = case_when(DIVISION==i$A ~ !!sym(i$B), T~output2))
  }
  db
}

#here are some replicates of your dataset to increase the number of lines
df60 = df[rep(seq_len(nrow(df)), 10),]
df600 = df[rep(seq_len(nrow(df)), 100),]
df6k = df[rep(seq_len(nrow(df)), 1000),]
df60k = df[rep(seq_len(nrow(df)), 10000),]
df600k = df[rep(seq_len(nrow(df)), 100000),]

#the benchmark of every function with every dataset
(mbm=microbenchmark(
  base = x1(df),
  base60 = df60 %>% x1,
  base600 = df600 %>% x1,
  base6k = df6k %>% x1,
  apply = x2(df),
  apply60 = df60 %>% x2,
  apply600 = df600 %>% x2,
  apply6k = df6k %>% x2,
  dplyr = x3(df),
  dplyr60 = x3(df60),
  dplyr600 = x3(df600),
  dplyr6k = x3(df6k),
  dplyr60k = x3(df60k),
  dplyr600k = x3(df600k),
  dplyrcw = x4(df),
  dplyrcw60 = x4(df60),
  dplyrcw600 = x4(df600),
  dplyrcw6k = x4(df6k),
  dplyrcw60k = x4(df60k),
  dplyrcw600k = x4(df600k),
  times=6
))

# Unit: microseconds
#        expr        min          lq        mean     median          uq        max neval  cld
#        base    515.283    599.3395    664.6767    683.396    739.3735    795.351     3 a   
#      base60   5125.835   5209.1620   5515.3047   5292.489   5710.0395   6127.590     3 a   
#     base600  53225.746  53300.1395  66678.0210  53374.533  73404.1585  93433.784     3  b  
#      base6k 587666.127 618005.9505 629841.8157 648345.774 650929.6600 653513.546     3    d
#       apply   1220.559   1272.8895   1342.4810   1325.220   1403.4420   1481.664     3 a   
#     apply60   2265.710   2384.9575   2497.3980   2504.205   2613.2420   2722.279     3 a   
#    apply600  10852.649  11579.6225  12047.9227  12306.596  12645.5595  12984.523     3 a   
#     apply6k 114463.342 125155.8980 137072.6593 135848.454 148377.3180 160906.182     3   c 
#       dplyr   1298.964   1352.9355   1433.0417   1406.907   1500.0805   1593.254     3 a   
#     dplyr60   1604.559   1647.0435   1713.2313   1689.528   1767.5675   1845.607     3 a   
#    dplyr600   1357.676   1456.6845   1556.4223   1555.693   1655.7955   1755.898     3 a   
#     dplyr6k   1954.644   1970.1425   2025.0260   1985.641   2060.2170   2134.793     3 a   
#    dplyr60k   6366.085   6584.1590   6809.2833   6802.233   7030.8825   7259.532     3 a   
#   dplyr600k  46893.576  53406.6235  58086.0983  59919.671  63682.3595  67445.048     3  b  
#     dplyrcw   5824.182   5834.0285   5999.5897   5843.875   6087.2935   6330.712     3 a   
#   dplyrcw60   5591.885   5683.0535   6032.4097   5774.222   6252.6720   6731.122     3 a   
#  dplyrcw600   5664.820   5811.2360   5900.6413   5957.652   6018.5520   6079.452     3 a   
#   dplyrcw6k   6390.883   6522.7120   9003.2733   6654.541  10309.4685  13964.396     3 a   
#  dplyrcw60k  14379.395  14936.6140  15179.6070  15493.833  15579.7130  15665.593     3 a   
# dplyrcw600k  85238.503  86607.3005  92601.6017  87976.098  96283.1510 104590.204     3  b  

Заключение

Для набора данных на 6 тыс. Строк

  • apply (137 с) в 6 раз быстрее, чем base (630 с)
  • ваниль dplyr даже намного быстрее (2 с)
  • Rlanged dplyr немного медленнее, чем ваниль (9 с)

Времена линейно расширяются с baseи apply при 100 мс / строка, поэтому 8M строк должны занимать приблизительно 8M секунд = 1 неделя.

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

...