How to easily format a frequency table in R?

Viewed 282

I am working with a data frame where each row is a patient with a particular illness. There is a column for their age category, and several columns with text (Yes or No) as to whether or not they are experiencing a particular symptom. Example provided below

set.seed(1)
Sick <- data.frame(age=sample(c("Infant", "Child", "Adult", "Elderly"), size=20, replace = TRUE),
                   cough= sample(c("Yes", "No"), size=20, replace = TRUE),
                   fever= sample(c("Yes", "No"), size=20, replace = TRUE),
                   chills= sample(c("Yes", "No"), size=20, replace = TRUE),
                   fatigue=sample(c("Yes", "No"), size=20, replace = TRUE))

What I am trying to get is a nicely structured frame that indicates how many patients in each category experience the symptom where the columns are the age categories and the rows are the count of how many people in that category experienced that symptom. The code below shows what I want my end result to be.

Count <- data.frame(symptom=c("cough", "fever", "chills", "fatigue"),
                    Infant=c(5, 1, 4, 2),
                    Child= c(4, 3, 2, 4),
                    Adult= c(2, 3, 1, 5),
                    Elderly = c(1, 0, 0, 0))

I know I could create this with the table and rbind functions, however, I was wondering if anyone had advice on how to streamline this? The real frame has about 10 age categories and 25 symptoms, so doing lots of tables may not be the most efficient.

Thank you for any and all help!

4 Answers

The above is great (upvoted, but tidyverse is needed as well), or even simpler

library(tidyverse)
Sick%>%
pivot_longer(-age,names_to='symptom')%>%
count(age,symptom)%>%
pivot_wider(names_from='symptom',values_from='n')

I've found in learning R that a great many problems can be solved by pivoting long and then wide or vice versa with some transform or calculation in between :)

I hope this is right. If I understand your question you just want to count the yes's for each category. I've put it into a function so just change x = Sick to whatever your dataframe is called and run the function.

EDIT I forget which package the pipe and columns_to_rownames comes from, I've added dplyr as a require but it may come from magrittr. If in doubt just load the tidyverse.


       sick_tbl <- function(x = Sick){
    require(dplyr)
    sick_piv <- pivot_longer(x, names_to = "names", values_to = "values",
                 -c(age))
    
    count <- sick_piv%>% 
      count(values, names, age) %>% 
      filter(values == "Yes") %>% 
    select(!values)
    
    
    
    sick_out <- pivot_wider(count,
                            names_from = "age",
                            values_from = "n") %>% 
      column_to_rownames(var = "names")
    
    sick_out[is.na(sick_out)] <- 0
    
    sick_out <<- sick_out}

To run on your example data:


    sick_tbl(x = Sick)
    
    
        Adult Child Elderly Infant
    chills      1     2       4     NA
    cough       4     2       5      1
    fatigue     3     3       2      1
    fever       2     4       2      2

Here are three pretty concise options:

Base R

# Can skip the lapply if the Y/N columns were character to begin with
with(subset(cbind(Sick[1], stack(lapply(Sick[-1], as.character))), values == "Yes"), 
     table(ind, age))

data.table

library(data.table)
melt(as.data.table(Sick), id.vars="age")[value == "Yes", table(variable, age)]

questionr

library(questionr)
cross.multi.table(Sick[-1], Sick[[1]], true.codes = list("Yes"))

First reshape, then use the datasummary function from the modelsummary package (self-promotion alert). The benefit of this solution is that you can customize the look of your table and save it to many formats (html, latex, word, markdown, etc.):

library('modelsummary')
library('tidyverse')

dat = pivot_longer(Sick, -age, names_to = "Symptom") %>%
      filter(value == "Yes")

datasummary(Symptom ~ N * age, data = dat) 
Adult Child Elderly Infant
chills 1 2 0 4
cough 2 4 1 5
fatigue 5 4 0 2
fever 3 3 0 1
Related