Как применить пользовательскую функцию к каждому столбцу матрицы? - PullRequest
0 голосов
/ 02 мая 2018

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

> inc_df[, 1:5]
          San Francisco Bayview Hunters Point Bernal Heights Castro/Upper Market Chinatown
2500-9999             22457                  1057            287                 329      1059
10000-14999           20708                   920            288                 463      1327
1500-19999            12701                   626            145                 148       867
20000-24999           12106                   491            285                 160       689
25000-29999           10129                   554            238                 328       167
30000-34999           10310                   338            257                 179       289
35000-39999            9028                   383            184                 163       326
40000-44999            9532                   472            334                 173       264
45000-49999            8406                   394            345                 241       193
50000-59999           17317                   727            367                 353       251
60000-74999           25947                  1037            674                 794       236
75000-99999           36378                  1185            980                 954       289
100000-124999         33890                   990            640                1208       199
125000-149999         24935                   522            666                 957       234
150000-199999         37190                   814           1310                1535       150
200000-250001         65763                   796           2122                3175       302

Функция выглядит следующим образом:

GroupedMedian <- function(frequencies, intervals, sep = NULL, trim = NULL) {
  # If "sep" is specified, the function will try to create the 
  #   required "intervals" matrix. "trim" removes any unwanted 
  #   characters before attempting to convert the ranges to numeric.
  if (!is.null(sep)) {
    if (is.null(trim)) pattern <- ""
    else if (trim == "cut") pattern <- "\\[|\\]|\\(|\\)"
    else pattern <- trim
    intervals <- sapply(strsplit(gsub(pattern, "", intervals), sep), as.numeric)
  }

  Midpoints <- rowMeans(intervals)
  cf <- cumsum(frequencies)
  Midrow <- findInterval(max(cf)/2, cf) + 1
  L <- intervals[1, Midrow]      # lower class boundary of median class
  h <- diff(intervals[, Midrow]) # size of median class
  f <- frequencies[Midrow]       # frequency of median class
  cf2 <- cf[Midrow - 1]          # cumulative frequency class before median class
  n_2 <- max(cf)/2               # total observations divided by 2

  unname(L + (n_2 - cf2)/f * h)
}

А код для применения функции выглядит так:

GroupedMedian(inc_df[, "Bernal Heights"], rownames(inc_df), sep="-", trim="cut")

Это все работает нормально, но я не могу понять, как применить это к каждому столбцу матрицы вместо того, чтобы вводить имя каждого столбца и запускать его снова и снова. Я пробовал это:

> minc_hood <- data.frame(apply(inc_df, 2, function(x) GroupedMedian(inc_df[, x], 
rownames(inc_df), sep="-", trim="cut")))

Но я получаю это сообщение об ошибке

Error in inc_df[, x] : subscript out of bounds

1 Ответ

0 голосов
/ 02 мая 2018

Здесь есть пара вещей:

  • совет: никогда не используйте apply с data.frame (если только вы не абсолютно уверены, что не возражаете против затрат на преобразование в matrix ^ 1 и можете принять потенциальную потерю данных ^ 2).

  • даже если вы собираетесь использовать apply, вы делаете это немного «выключено»: когда вы говорите apply(df, 2, func), он берет первый столбец df и представляет его как аргументы, например,

    apply(mtcars, 2, mean)
    

    будет звонить как

    mean(c(21, 21, 22.8, 21.4, 18.7, ...)) # mpg
    mean(c(6, 6, 4, 6, 8, ...))            # cyl
    mean(c(160, 160, 108, 258, 360, ...))  # disp
    # ... etc
    

    В этом контексте вы используете apply(inc_df, 2, function(x) GroupedMedian(inc_df[, x], ...)) неправильно, поскольку x заменяется всеми значениями первого столбца inc_df (а затем всеми значениями 2-го столбца и т. Д.).

Поскольку ваша функция выглядит так, как будто она принимает вектор значений (плюс некоторые другие аргументы), я предлагаю вам попробовать что-то вроде

inc_df[] <- lapply(inc_df, GroupedMedian, rownames(inc_df), sep="-", trim="cut")

Если вы хотите применить эту функцию к подмножеству этих столбцов, то что-то вроде этого работает хорошо:

ind <- c(1,3,7)
inc_df[ind] <- lapply(inc_df[ind], GroupedMedian, rownames(inc_df), sep="-", trim="cut")

Использование inc_df[] <- ... (когда не выполняется подмножество столбцов) гарантирует, что мы заменим значения столбцов, не теряя атрибута, который является data.frame. Фактически это то же самое, что и inc_df <- as.data.frame(...) с некоторыми другими незначительными нюансами.

Примечания:

^ 1: apply всегда преобразует data.frame в matrix. Это может быть хорошо, но с большими данными займет ненулевое количество времени. Это также может иметь последствия, см. Далее ...

^ 2: matrix может иметь только один класс, в отличие от data.frame. Это означает, что все столбцы будут преобразованы с повышением до общего общего типа, порядка logical < integer < numeric < POSIXct < character. Это означает, что если у вас есть все numeric столбцы и один character, то функция, которую вы apply используете, будет видеть все character данные. Этого можно избежать, выбрав только те столбцы с ожидаемыми вами типами, например:

isnum <- sapply(inc_df, is.numeric)
inc_df[isnum] <- apply(inc_df[isnum], 2, GroupedMedian, ...)

и в этом случае худшее преобразование, которое вы получите, будет integer - в numeric, вероятно, приемлемое (и обратимое) преобразование.

...