2014-05-19 9 views
6

In questo momento, ho il seguente data.frame che è stato creato da original.df %.% group_by(Category) %.% tally() %.% arrange(desc(n)).Creazione di un campo "altro"

DF <- structure(list(Category = c("E", "K", "M", "L", "I", "A", 
"S", "G", "N", "Q"), n = c(163051, 127133, 106680, 64868, 49701, 
47387, 47096, 45601, 40056, 36882)), .Names = c("Category", 
"n"), row.names = c(NA, 10L), class = c("tbl_df", "tbl", "data.frame" 
)) 

     Category  n 
1    E 163051 
2    K 127133 
3    M 106680 
4    L 64868 
5    I 49701 
6    A 47387 
7    S 47096 
8    G 45601 
9    N 40056 
10    Q 36882 

Desidero creare un campo "Altro" dalle Categorie in basso classificate per n. vale a dire

 Category  n 
1    E 163051 
2    K 127133 
3    M 106680 
4    L 64868 
5    I 49701 
6   Other 217022 

In questo momento, sto facendo

rbind(filter(DF, rank(rev(n)) <= 5), 
    summarise(filter(DF, rank(rev(n)) > 5), Category = "Other", n = sum(n))) 

che crolla tutte le categorie non nella top 5 nella categoria Altro.

Ma sono curioso di sapere se c'è un modo migliore in dplyr o qualche altro pacchetto esistente. Con "meglio" intendo più succinta/leggibile. Sono anche interessato a metodi più intelligenti o più flessibili per scegliere Other.

risposta

5

differente pacchetto/diversa versione sintassi:

library(data.table) 

dt = as.data.table(DF) 

dt[order(-n), # your data is already sorted, so this does nothing for it 
    if (.BY[[1]]) .SD else list("Other", sum(n)), 
    by = 1:nrow(dt) <= 5][, !"nrow", with = F] 
# Category  n 
#1:  E 163051 
#2:  K 127133 
#3:  M 106680 
#4:  L 64868 
#5:  I 49701 
#6: Other 217022 
6

Questo è un altro approccio, assumendo che ciascuna categoria (della parte superiore 5 almeno) si verifica una sola volta:

df %.% 
    arrange(desc(n)) %.%  #you could skip this step since you arranged the input df already according to your question 
    mutate(Category = ifelse(1:n() > 5, "Other", Category)) %.% 
    group_by(Category) %.% 
    summarize(n = sum(n)) 

# Category  n 
#1  E 163051 
#2  I 49701 
#3  K 127133 
#4  L 64868 
#5  M 106680 
#6 Other 217022 

Edit:

Ho appena notato che la mia uscita non è ordine diminuendo n più. Dopo aver eseguito nuovamente il codice, ho scoperto che l'ordine viene mantenuto fino a dopo il group_by(Category) ma quando eseguo il summarize in seguito, l'ordine è scomparso (o meglio, sembra essere ordinato da Category). Dovrebbe essere così?

Ecco altri tre modi:

m <- 5 #number of top results to show in final table (excl. "Other") 
n <- m+1 

#preserves the order (or better: reesatblishes it by index) 
df <- arrange(df, desc(n)) %.% #this could be skipped if data already ordered 
    mutate(idx = 1:n(), Category = ifelse(idx > m, "Other", Category)) %.% 
    group_by(Category) %.% 
    summarize(n = sum(n), idx = first(idx)) %.% 
    arrange(idx) %.% 
    select(-idx) 

#doesnt preserve the order (same result as in first dplyr solution, ordered by Category) 
df[order(df$n, decreasing=T),]  #this could be skipped if data already ordered 
df[n:nrow(df),1] <- "Other" 
df <- aggregate(n ~ Category, data = df, FUN = "sum") 

#preserves the order (without extra index) 
df[order(df$n, decreasing=T),]  #this could be skipped if data already ordered 
df[n:nrow(df),1] <- "Other" 
df[n,2] <- sum(df$n[df$Category == "Other"]) 
df <- df[1:n,] 
0

Questa funzione modifica una colonna, sostituendo le voci infrequenti con Other, sia specificando una frequenza minima o specificando il numero risultante di categorie intese.

#' @title Group infrequent entries into 'Other category' 
#' @description Useful when you want to constrain the number of unique values in a column. 
#' @param .data Data containing variable. 
#' @param var Variable containing infrequent entries, to be collapsed into "Other". 
#' @param n Threshold for total number of categories above "Other". 
#' @param count Threshold for total count of observations before "Other". 
#' @param by Extra variables to group by when calculating \code{n} or \code{count}. 
#' @param copy Should \code{.data} be copied? Currently only \code{TRUE} is supported. 
#' @param other.category Value that infrequent entries are to be collapsed into. Defaults to \code{"Other"}. 
#' @return \code{.data} but with \code{var} changed to be grouped into smaller categories. 
#' @export 
mutate_other <- function(.data, var, n = 5, count, by = NULL, copy = TRUE, other.category = "Other"){ 
    stopifnot(is.data.table(.data), 
      is.character(other.category), 
      identical(length(other.category), 1L)) 

    had.key <- haskey(.data) 

    if (!isTRUE(copy)){ 
    stop("copy must be TRUE") 
    } 

    out <- copy(.data) 

    if (had.key){ 
    orig_key <- key(out) 
    } else { 
    orig_key <- "_order" 
    out[, "_order" := 1:.N] 
    setkeyv(out, "_order") 
    } 

    if (is.character(.data[[var]])){ 
    stopifnot(!("nvar" %in% names(.data)), 
       var %in% names(.data)) 

    N <- .rank <- NULL 
    n_by_var <- 
     out %>% 
     .[, .N, keyby = c(var, by)] %>% 
     .[, .rank := rank(-N)] 

    out <- merge(out, n_by_var, by = c(var, by)) 

    if (missing(count)){ 
     out[, (var) := dplyr::if_else(.rank <= n, out[[var]], other.category)] 
    } else { 
     out[, (var) := dplyr::if_else(N >= count, out[[var]], other.category)] 
    } 
    out <- 
     out %>% 
     .[, N := NULL] %>% 
     .[, .rank := NULL] 

    setkeyv(out, orig_key) 

    if (!had.key){ 
     out[, (orig_key) := NULL] 
     setkey(out, NULL) 
    } 
    out 

    } else { 
    warning("Attempted to use by = on a non-character vector. Aborting.") 
    return(.data) 
    } 
} 

https://github.com/HughParsonage/hutils/blob/master/R/mutate_other.R

Problemi correlati