For example, I have a data frame consisting of columns ID, Genotype, Sex, time (days), event (deaths), for a number of Drosophila strains of both sexes, where deaths can be anywhere from 0 to 2, 3, etc. Apologies as this is probably a very simple solution, but most tutorials about survival analysis don't answer it and only use datasets where the event is either 0 or 1. Have tried typical loading of data frame and packages (this part seems fine) and then assign survival object and plot with ggsurvplot.
-
4$\begingroup$ You have an aggregated dataset. Expand it into an individual level dataset. Then run a standard survival analysis. For example, if you a record with three deaths, convert this to three records, each with a single death event.. In fact, the way you describe your data, it sounds like you don’t have any censoring events. If so, you don’t really need survival analysis… $\endgroup$– LimeyCommented Jun 29, 2023 at 5:26
-
$\begingroup$ @Limey thanks! Any recommendations on how to do that? $\endgroup$– PlsnobullyCommented Jun 29, 2023 at 15:06
-
$\begingroup$ Poisson regression might be able to handle that. Why don't you post a sample dataset? Tested code is more likely to be offered when we have an unambiguous example on which we might apply methods. $\endgroup$– DWinCommented Jul 1, 2023 at 3:36
2 Answers
Expanding the data set into one row per individual, as suggested in a comment and in another answer, is a very good choice in this situation and should be both reliable and extensible to more complicated situations.
For reference, it's also possible to use the weights
argument to the R coxph()
function to accomplish this. According to the help page:
Case weights are treated as replication weights, i.e., a case weight of 2 is equivalent to having 2 copies of that subject's observation.
In your situation, you would have an event marker of 1 for death at a particular time and set of covariate values in a data row, but specify the number of individuals having the event and that set of covariate values as the weight value in that row. You could similarly use the weights
argument, with an event marker of 0, for the number of flies that survived to the final observation time, for each set of covariate values.
Assuming your survival data looks like this:
ID | Genotype | Sex | time (days) | event (deaths) |
---|---|---|---|---|
Control_Vial1 | Control | F | 10 | 1 |
Control_Vial2 | Control | M | 10 | 1 |
Option 1: Use the ggbulksurv
package at https://github.com/qhuitan/ggbulksurv - I wrote it specifically for this scenario. ggbulksurv::get_indiv_surv()
would do the trick.
In order to make your data work with this package, you have to add a column called condition
which would reflect the sex. Your input data should look like this:
condition | ID | Genotype | Sex | day | dead |
---|---|---|---|---|---|
control_F | Control_Vial1 | Control | F | 10 | 3 |
control_M | Control_Vial2 | Control | M | 10 | 2 |
Option 2: Manually code this using tidyr::uncount()
I'm assuming you have no censored data (that is, no flies have escaped/ gone missing/ gotten stuck in food).
Renaming some columns for easy reference:
ID | genotype | sex | day | dead |
---|---|---|---|---|
Control_Vial1 | Control | F | 10 | 3 |
Control_Vial2 | Control | M | 10 | 2 |
sample_data <- read.csv("path-to-file.csv")
# Add a condition column
sample_data <- sample_data %>%
tidyr::unite("condition", genotype:sex, remove = FALSE)
## -- Calculate dead -- ##
df_dead <- sample_data %>%
# Select only dead column
dplyr::group_by(condition) %>%
# Uncount so that each row becomes an individual
tidyr::uncount(dead) %>%
# Add status = 1 (dead)
dplyr::mutate(status = 1)
If you have censored data, use this code instead:
ID | genotype | sex | day | dead | censored |
---|---|---|---|---|---|
Control_Vial1 | Control | F | 10 | 3 | 2 |
Control_Vial2 | Control | M | 10 | 2 | 5 |
## -- Calculate dead -- ##
df_dead <- sample_data %>%
# Select only dead column
dplyr::group_by(condition) %>%
dplyr::select(-c(censored)) %>%
# Uncount so that each row becomes an individual
tidyr::uncount(dead) %>%
# Add status = 1 (dead)
dplyr::mutate(status = 1)
## -- Calculate censored -- ##
df_censored <- sample_data %>%
# Select only censored column
dplyr::group_by(condition) %>%
dplyr::select(-c(dead)) %>%
# Uncount so that each row becomes an individual
tidyr::uncount(censored) %>%
# Add status = 0 (censored)
dplyr::mutate(status = 0)
## -- Combine -- ##
df_combined <- dplyr::full_join(df_censored, df_dead,
join_by("condition", "day", "status")) %>%
dplyr::arrange(day) %>%
dplyr::ungroup()