Подтвердить что ты не робот

Использование атрибутов `ftable` для извлечения данных

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

Скажем, мы начинаем с:

mytable <- ftable(Titanic, row.vars = 1:3)
mytable
##                    Survived  No Yes
## Class Sex    Age                   
## 1st   Male   Child            0   5
##              Adult          118  57
##       Female Child            0   1
##              Adult            4 140
## 2nd   Male   Child            0  11
##              Adult          154  14
##       Female Child            0  13
##              Adult           13  80
## 3rd   Male   Child           35  13
##              Adult          387  75
##       Female Child           17  14
##              Adult           89  76
## Crew  Male   Child            0   0
##              Adult          670 192
##       Female Child            0   0
##              Adult            3  20

str(mytable)
##  ftable [1:16, 1:2] 0 118 0 4 0 154 0 13 35 387 ...
##  - attr(*, "row.vars")=List of 3
##   ..$ Class: chr [1:4] "1st" "2nd" "3rd" "Crew"
##   ..$ Sex  : chr [1:2] "Male" "Female"
##   ..$ Age  : chr [1:2] "Child" "Adult"
##  - attr(*, "col.vars")=List of 1
##   ..$ Survived: chr [1:2] "No" "Yes"
## NULL

Поскольку нет dimnames, я не могу извлекать данные так же, как если бы я имел объект с dimnames. Например, для меня нет возможности напрямую извлекать все значения "Ребенок" из классов "1-й" и "3-й".

Мой текущий подход состоит в том, чтобы преобразовать его в table, выполнить извлечение, а затем преобразовать его обратно в ftable.

Пример:

mytable[c("1st", "3rd"), , "Child", ]
## Error: incorrect number of dimensions

## Only the underlying data are seen as having dims
dim(mytable)
## [1] 16  2

## I'm OK with the "Age" column being dropped in this case....
ftable(as.table(mytable)[c("1st", "3rd"), , "Child", ])
##              Survived No Yes
## Class Sex                   
## 1st   Male             0   5
##       Female           0   1
## 3rd   Male            35  13
##       Female          17  14

Однако мне не нравится этот подход, потому что общий макет иногда меняется, если вы не будете осторожны. Сравните его со следующим, что устраняет требование подмножества только детей и добавляет требование подмножества только тех, кто не выжил:

ftable(as.table(mytable)[c("1st", "3rd"), , , "No"])
##              Age Child Adult
## Class Sex                   
## 1st   Male           0   118
##       Female         0     4
## 3rd   Male          35   387
##       Female        17    89

Мне не нравится, что общая компоновка строк и столбцов изменилась. Это классический случай необходимости запоминать использование drop = FALSE для сохранения измерений при извлечении одного столбца:

ftable(as.table(mytable)[c("1st", "3rd"), , , "No", drop = FALSE])
##                    Survived  No
## Class Sex    Age               
## 1st   Male   Child            0
##              Adult          118
##       Female Child            0
##              Adult            4
## 3rd   Male   Child           35
##              Adult          387
##       Female Child           17
##              Adult           89

Я знаю, что есть много способов получить нужные мне данные, начиная с подмножества из необработанных данных и затем создавая ftable, но для этого вопроса допустим, что это невозможно.

Конечная цель состоит в том, чтобы иметь подход, который позволяет мне извлекать из ftable, сохраняя формат отображения вложенной иерархии строк.

Существуют ли другие решения? Можем ли мы использовать атрибуты row.vars и col.vars для извлечения данных из ftable и сохранить его форматирование?


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

Пример:

tab2 <- ftable(Titanic, row.vars = 1:2, col.vars = 3:4)
tab2
##              Age      Child     Adult    
##              Survived    No Yes    No Yes
## Class Sex                                
## 1st   Male                0   5   118  57
##       Female              0   1     4 140
## 2nd   Male                0  11   154  14
##       Female              0  13    13  80
## 3rd   Male               35  13   387  75
##       Female             17  14    89  76
## Crew  Male                0   0   670 192
##       Female              0   0     3  20

Обратите внимание на вложенность "Возраст" и "Выжил".

Попробуйте мой текущий подход:

ftable(as.table(tab2)[c("1st", "3rd"), , , , drop = FALSE])
##                    Survived  No Yes
## Class Sex    Age                   
## 1st   Male   Child            0   5
##              Adult          118  57
##       Female Child            0   1
##              Adult            4 140
## 3rd   Male   Child           35  13
##              Adult          387  75
##       Female Child           17  14
##              Adult           89  76

Я могу вернуться к тому, что хочу:

ftable(as.table(tab2)[c("1st", "3rd"), , , , drop = FALSE], row.vars = 1:2, col.vars = 3:4)

Но я надеюсь на что-то более прямое.

4b9b3361

Ответ 1

Вот что мне удалось раздобыть вместе с некоторой помощью от Axeman:

replace_empty_arguments <- function(a) {
  empty_symbols <- vapply(a, function(x) {
    is.symbol(x) && identical("", as.character(x)), 0)
  } 
  a[!!empty_symbols] <- 0
  lapply(a, eval)
}

`[.ftable` <- function (inftable, ...) {
  if (!class(inftable) %in% "ftable") stop("input is not an ftable")
  tblatr <- attributes(inftable)[c("row.vars", "col.vars")]
  valslist <- replace_empty_arguments(as.list(match.call()[-(1:2)]))
  x <- sapply(valslist, function(x) identical(x, 0))
  TAB <- as.table(inftable)
  valslist[x] <- dimnames(TAB)[x]
  temp <- as.matrix(expand.grid(valslist))
  out <- ftable(
    `dimnames<-`(`dim<-`(TAB[temp], lengths(valslist)), valslist),
    row.vars = seq_along(tblatr[["row.vars"]]),
    col.vars = seq_along(tblatr[["col.vars"]]) + length(tblatr[["row.vars"]]))
  names(attributes(out)[["row.vars"]]) <- names(tblatr[["row.vars"]])
  names(attributes(out)[["col.vars"]]) <- names(tblatr[["col.vars"]])
  out
}

Попробуйте с примерами из вопроса:

mytable[c("1st", "3rd"), , "Child", ]
##                    Survived No Yes
## Class Sex    Age                  
## 1st   Male   Child           0   5
##       Female Child           0   1
## 3rd   Male   Child          35  13
##       Female Child          17  14

mytable[c("1st", "3rd"), , , "No"]
##                    Survived  No
## Class Sex    Age               
## 1st   Male   Child            0
##              Adult          118
##       Female Child            0
##              Adult            4
## 3rd   Male   Child           35
##              Adult          387
##       Female Child           17
##              Adult           89

tab2[c("1st", "3rd"), , , ]
##              Age      Child     Adult    
##              Survived    No Yes    No Yes
## Class Sex                                
## 1st   Male                0   5   118  57
##       Female              0   1     4 140
## 3rd   Male               35  13   387  75
##       Female             17  14    89  76

Ответ 2

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

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

Вот подход, использующий пакет tables, который сохраняет иерархическую структуру данных Titanic, а также исключает ячейки, которые пусты, когда мы подмножаем фрейм данных.

Сначала мы передаем входящую таблицу как фрейм данных, чтобы мы могли ее подмножить во время tabular().

 library(titanic)
 df <- as.data.frame(Titanic)

Затем мы используем tables::tabular(), подмножая данные в аргументе data= с оператором extract [ и используем DropEmpty(), чтобы избежать печати строк и столбцов, где Freq == 0. Мы также используем Heading() для подавления нежелательных заголовков для Freq и sum.

tabular((Class * Sex) ~ (Age)*Survived*Heading()*Freq*Heading()*sum*DropEmpty(empty=0),
        data=df[df$Class %in% c("1st","3rd") & df$Age=="Child",])

... и вывод:

> tabular((Class * Sex) ~ (Age)*Survived*Heading()*Freq*Heading()*sum*DropEmpty(empty=0),
+         data=df[df$Class %in% c("1st","3rd") & df$Age=="Child",])

              Age         
              Child       
              Survived    
 Class Sex    No       Yes
 1st   Male    0        5 
       Female  0        1 
 3rd   Male   35       13 
       Female 17       14

Если мы удалим DropEmpty(), мы реплицируем всю табличную структуру на основе переменных факторов в таблице.

> # remove DropEmpty() to replicate entire factor structure
> tabular((Class * Sex) ~ (Age)*Survived*Heading()*Freq*Heading()*sum,
+         data=df[df$Class %in% c("1st","3rd") & df$Age=="Child",])

              Age                      
              Child        Adult       
              Survived     Survived    
 Class Sex    No       Yes No       Yes
 1st   Male    0        5  0        0  
       Female  0        1  0        0  
 2nd   Male    0        0  0        0  
       Female  0        0  0        0  
 3rd   Male   35       13  0        0  
       Female 17       14  0        0  
 Crew  Male    0        0  0        0  
       Female  0        0  0        0  
> 

Репликация второго и третьего примеров из OP также проста.

> # second example from question
> tabular((Class * Sex * Age) ~ Survived*Heading()*Freq*Heading()*sum*DropEmpty(empty=0),
+         data=df[df$Class %in% c("1st","3rd") & df$Survived=="No",])

                    Survived
 Class Sex    Age   No      
 1st   Male   Child   0     
              Adult 118     
       Female Child   0     
              Adult   4     
 3rd   Male   Child  35     
              Adult 387     
       Female Child  17     
              Adult  89     
> # third example from question 
> tabular((Class * Sex) ~ (Age)*Survived*Heading()*Freq*Heading()*sum*DropEmpty(empty=0),
+         data=df[df$Class %in% c("1st","3rd"),])

              Age                      
              Child        Adult       
              Survived     Survived    
 Class Sex    No       Yes No       Yes
 1st   Male    0        5  118       57
       Female  0        1    4      140
 3rd   Male   35       13  387       75
       Female 17       14   89       76
>