использовать sapply для пользовательских функций в R

#r #dplyr #tidyverse #apply #purrr

#r #dplyr #tidyverse #применить #purrr

Вопрос:

(Использование mtcars и iris для воспроизводимости)

Я создал функцию R get_col_info для поиска сводки данных, которая соответствует приведенной ниже:

  1. Если столбец numeric/integer/double равен, то получаем минимальное, максимальное, среднее

  2. Если столбец character/factor , то получите количество уникальных значений и уникальных значений

      get_col_info <- function(data,col_name) {  
    
      c_name <- c(col_name)
      s <- data[,c_name]
      type <- typeof(s)
    
      if(type %in% c("numeric","double","integer")){
       min <- min(s)
       max <- max(s)
       mean <- mean(s)
       aa <- list(min=min, max=max,mean=mean)
       return(aa)
      }
    
     if(type %in% c("character","factor")){
      uni <- unique(s)
      len <- length(uni)
      aa <- list(n_values=len,unique_values=c(uni))
      return(aa)}
     }
    
    get_col_info(mtcars, "mpg")
    get_col_info(iris, "Petal.Width")
    get_col_info(iris, "Species")
      

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

Однако основной запрос теперь заключается в том, что я хочу запустить эту функцию для всех имен столбцов сразу, что-то вроде sapply(iris,mean) , но я не уверен, как это сделать, потому что функция принимает dataframe amp; имя столбца. Я пытался это сделать, но это выдает ошибку

 sapply(iris,get_col_info(iris,names(iris)))

Error in match.fun(FUN) : 
  'get_col_info(iris, names(iris))' is not a function, character or symbol
  

Оба решения apply и purrr приветствуются. Я также ищу кого-нибудь, кто мог бы рассказать мне, как я мог бы лучше написать свою функцию, я подозреваю, что созданный мной c_name не является идеальным способом получения имен столбцов.

Комментарии:

1. с помощью вашей написанной функции вы могли бы сделать: sapply(c("Petal.Length","Petal.Width"), get_col_info, data=iris)

Ответ №1:

Вы должны использовать class для проверки типа, а не typeof :

 get_col_info <- function(data,col_name) {    
  s <- data[,col_name]
  type <- class(s)
  if(type %in% c("numeric","double","integer")){
    min <- min(s)
    max <- max(s)
    mean <- mean(s)
    aa <- list(min=min, max=max,mean=mean)
    return(aa)
  }
  else if(type %in% c("character","factor")){
    uni <- as.character(unique(s))
    len <- length(uni)
    aa <- list(n_values=len,unique_values=uni)
    return(aa)
  }
}
  

Проверка выходных данных :

 get_col_info(mtcars, "mpg")
#$min
#[1] 10.4

#$max
#[1] 33.9

#$mean
#[1] 20.09062

get_col_info(iris, "Species")
#$n_values
#[1] 3

#$unique_values
#[1] "setosa"     "versicolor" "virginica" 
  

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

 sapply(names(iris), get_col_info, data = iris)
  

Или замените sapply на map , если вас интересует purrr решение.


Другим способом было бы передавать значения столбцов напрямую вместо имени.

 get_col_info <- function(s) {    
  if(is.numeric(s)) {
    min <- min(s)
    max <- max(s)
    mean <- mean(s)
    aa <- list(min=min, max=max,mean=mean)
    return(aa)
  }
  else {
    uni <- as.character(unique(s))
    len <- length(uni)
    aa <- list(n_values=len,unique_values=uni)
    return(aa)
  }
}

sapply(iris, get_col_info)
  

Комментарии:

1. Честно, получил первую часть. Я проверю разницу между ними. Но основная часть заключается в том, что теперь я хочу запустить эту функцию для всех столбцов сразу. Как я могу это сделать с именованным столбцом в качестве имени списка

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

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

Ответ №2:

Вы можете сделать это с помощью summarise and across , с проверкой типа (например is.numeric ):

 library(dplyr)

iris %>%
  summarise(across(where(is.numeric), list(min=min, max=max, mean=mean)),
            across(where(~is.factor(.) | is.character(.)), 
                   list(n_values = ~length(unique(.)), 
                        unique_values = ~as.character(unique(.))))) %>%
  glimpse()
  

Вывод:

 Rows: 3
Columns: 14
$ Sepal.Length_min      <dbl> 4.3, 4.3, 4.3
$ Sepal.Length_max      <dbl> 7.9, 7.9, 7.9
$ Sepal.Length_mean     <dbl> 5.843333, 5.843333, 5.843333
$ Sepal.Width_min       <dbl> 2, 2, 2
$ Sepal.Width_max       <dbl> 4.4, 4.4, 4.4
$ Sepal.Width_mean      <dbl> 3.057333, 3.057333, 3.057333
$ Petal.Length_min      <dbl> 1, 1, 1
$ Petal.Length_max      <dbl> 6.9, 6.9, 6.9
$ Petal.Length_mean     <dbl> 3.758, 3.758, 3.758
$ Petal.Width_min       <dbl> 0.1, 0.1, 0.1
$ Petal.Width_max       <dbl> 2.5, 2.5, 2.5
$ Petal.Width_mean      <dbl> 1.199333, 1.199333, 1.199333
$ Species_n_values      <int> 3, 3, 3
$ Species_unique_values <chr> "setosa", "versicolor", "virginica"
  

Примечание: я добавил glimpse() , чтобы сделать вывод более читаемым, в этом нет необходимости.