2014-05-19 13 views
6

W tej chwili mam następujące data.frame, który został utworzony przez original.df %.% group_by(Category) %.% tally() %.% arrange(desc(n)).Tworzenie "innego" pola

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 

Chcę utworzyć pole "Inne" od dołu w rankingu Kategorie wg n. tj

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

Teraz robie

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

który załamuje wszystkie kategorie nie w top 5 do drugiej kategorii.

Ale jestem ciekawy, czy istnieje lepszy sposób w dplyr lub w innym istniejącym pakiecie. Przez "lepsze" mam na myśli bardziej zwięzłe/czytelne. Interesują mnie również metody o sprytniejszych lub bardziej elastycznych sposobach wyboru Other.

Odpowiedz

5

inny pakiet/inna wersja Składnia

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

To kolejne podejście, przy założeniu, że każda kategoria (z top 5 co najmniej) występuje tylko wtedy, gdy:

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:

Właśnie zauważyłem, że moje wyjście nie jest zamówienie poprzez zmniejszenie n nie więcej. Po ponownym uruchomieniu kodu dowiedziałem się, że zamówienie jest utrzymywane do momentu po group_by(Category), ale po uruchomieniu summarize po tym czasie zamówienie zniknęło (lub raczej wydaje się być uporządkowane przez Category). Czy to powinno tak być?

tu jeszcze trzy sposoby:

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

Funkcja modyfikuje kolumnę wymiany rzadkich dane z Other, albo przez określenie minimalnej częstotliwości lub poprzez określenie docelowej liczby zamierzonych kategorii.

#' @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

Powiązane problemy