Titanic Atlas Sweep 1781365826396
Executive Summary

Executive Summary

Executive summary

Observations
1307
overall_survival_rate
0.2601
overall_survival_pct
26.0138
Predictors
8
model_p_value
0
Among 1,307 Titanic passengers, 26.0% survived. The strongest independent predictor of survival is Sex: 1 with an odds ratio of 6.60. Logistic regression identified multiple demographic and socioeconomic factors that independently predict survival outcomes.
Suggested Interpretation

Among 1,307 Titanic passengers, 26.0% survived. The strongest independent predictor of survival is Sex: 1 with an odds ratio of 6.60. Logistic regression identified multiple demographic and socioeconomic factors that independently predict survival outcomes.

Overview

Analysis Overview

Analysis overview and framework setup

Observations Analyzed1307
Overall Survival Rate (%)26
Model TypeLogistic Regression
Predictor Terms8
Suggested Interpretation

Statistical analysis of 1,307 Titanic passengers to identify demographic and socioeconomic factors predicting survival. Overall, 26.0% of passengers survived. Logistic regression quantifies the independent contribution of predictor terms derived from six variables—passenger class, sex, age, family size, fare, and embarkation port—producing 8 total model coefficients while controlling for correlated factors. The model achieves strong statistical significance.

Data Preparation

Data Quality

Row accounting and preprocessing steps applied before analysis.

Initial Rows1309
Final Rows1307
Rows Removed2
Suggested Interpretation

Loaded 1,309 passenger records. Removed 2 rows with missing age values; 1,307 rows (99.8%) used for analysis. Sex is encoded as an integer (0 = Male, 1 = Female). Passenger class is encoded as an integer (1 = First Class, 2 = Second Class, 3 = Third Class). Survival is a binary integer (0 = Did Not Survive, 1 = Survived).

Visualization

Survival Rate by Passenger Group

Observed survival rates by sex and passenger class

Suggested Interpretation

Female — 2nd Class showed the highest survival rate at 66%, while Male — 3rd Class had the lowest at 9.5%. The overall survival rate was 26%. Significant disparities in survival outcomes are evident across demographic groups.

Data Table

Top Survival Drivers — Logistic Regression

Odds ratios and 95% confidence intervals from logistic regression model

PredictorOdds RatioCI LowerCI Upper
Sex: 16.5994.9298.895
Class: 30.20670.13030.326
Class: 20.51130.32260.8075
Family Size0.89540.80070.9938
Embarked: 20.89730.62621.294
Age0.96970.95770.9815
Embarked: 11.0020.55531.79
Fare0.99970.99651.003
Suggested Interpretation

Logistic regression quantifies the independent association between each passenger attribute and survival, controlling for all other predictors in the model. Odds ratios (OR) measure the multiplicative change in survival odds for a unit increase (or factor-level change) in the predictor. An OR > 1 indicates increased survival odds; OR < 1 indicates decreased odds. Of the 8 predictors modeled, 2 are associated with higher survival odds and 6 with lower odds. The strongest driver is Sex: 1 (OR = 6.599, 95% CI [4.93, 8.89]), which increases the odds of survival. Narrow confidence intervals indicate precise estimates; wide intervals reflect uncertainty.

Visualization

Survival Rate by Passenger Class

Observed survival rates stratified by passenger class, showing the gradient between economic tiers.

Suggested Interpretation

Survival rates varied substantially across the three passenger classes. Among the 1,307 passengers analyzed, 1st Class passengers had the highest survival rate at 41.7% (n=321), while 3rd class passengers had the lowest at 16.8% (n=709). The gradient between the highest and lowest tiers was 25.0 percentage points. This steep differential—far exceeding the 26.0% overall survival rate—indicates that passenger class was a dominant determinant of survival probability, with first-class passengers dramatically more likely to survive than third-class passengers.

Visualization

Age Distribution by Survival Status

Passenger age distribution stratified by survival status.

Suggested Interpretation

Among 1,307 passengers analyzed, those who survived (n=340) had a median age of 28 years (mean: 28.2; range: 0-80), compared to 28 years for non-survivors (n=967; mean: 29.9; range: 0-76). The median age difference of 0 years between groups indicates subtle variation in age distribution. However, both groups share the same median age of 28 years, suggesting overall age composition differed between survival groups.

Methodology

Methodology

Statistical methodology and diagnostics for Titanic Survival — Passenger Drivers

Statistical Method

Titanic Survival — Passenger Drivers

Analyses Titanic passenger records to surface the demographic and socioeconomic factors that differentiate survivors from non-survivors. Logistic regression quantifies each predictor's independent contribution as odds ratios with 95% confidence intervals, while targeted visualisations compare survival rates across passenger class and sex and contrast age distributions between outcome groups.

Software & Citation
MCP Analytics · mcpanalytics.ai
Code Appendix

Analysis Code

Complete R source code for this analysis

Titanic Survival — Passenger Drivers

Analyses Titanic passenger records to surface the demographic and socioeconomic factors that differentiate survivors from non-survivors. Logistic regression quantifies each predictor's independent contribution as odds ratios with 95% confidence intervals, while targeted visualisations compare survival rates across passenger class and sex and contrast age distributions between outcome groups.

Why This Method?

Logistic regression models the binary survival outcome directly and produces odds ratios that quantify each predictor's independent effect while adjusting for other factors. This approach isolates the contribution of passenger class, sex, age, and family composition to survival risk.

What This Analysis Covers

  • Executive Summary: overall survival rate and strongest independent drivers
  • Survival Rate by Group: sex × class breakdown of observed rates
  • Top Survival Drivers: logistic regression odds ratios with 95% CIs
  • Class Survival Curve: gradient across passenger classes
  • Age Distribution: age spread by survival status
suppressPackageStartupMessages(library(htmltools))
suppressPackageStartupMessages(library(jsonlite))
suppressPackageStartupMessages(library(plotly))
suppressPackageStartupMessages(library(DT))
suppressPackageStartupMessages(library(htmlwidgets))
suppressPackageStartupMessages(library(arrow))
suppressPackageStartupMessages(library(knitr))
suppressPackageStartupMessages(library(rmarkdown))
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(tidyr))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(stringr))
suppressPackageStartupMessages(library(lubridate))
suppressPackageStartupMessages(library(broom))
suppressPackageStartupMessages(library(survival))
suppressPackageStartupMessages(library(Matrix))
suppressPackageStartupMessages(library(cluster))
suppressPackageStartupMessages(library(data.table))

Step 1: Data Preparation and Row Accounting

Capture initial size, handle missing values, prepare factors.

initial_rows <- nrow(df)

  # Remove rows with missing age (needed for logistic regression)
  df_clean <- na.omit(df[, c("survived", "pclass", "sex", "age", "sibsp", "parch", "embarked", "fare")])
  final_rows <- nrow(df_clean)
  rows_removed <- initial_rows - final_rows

Step 2: Encode Target and Predictors as Factors

Classification requires factor encoding before glm().

df_clean$survived <- as.factor(df_clean$survived)
  df_clean$pclass <- as.factor(df_clean$pclass)
  df_clean$sex <- as.factor(df_clean$sex)
  df_clean$embarked <- as.factor(df_clean$embarked)

  # Derive family size (family_size = sibsp + parch + 1)
  df_clean$family_size <- df_clean$sibsp + df_clean$parch + 1L

Step 3: Logistic Regression Model

Fit glm with tryCatch to handle potential issues (quasi-separation, singular matrix).

model <- tryCatch(
    glm(survived ~ pclass + sex + age + family_size + fare + embarked,
        family = binomial(link = "logit"),
        data = df_clean),
    error = function(e) NULL
  )

Step 4: Extract Odds Ratios and Confidence Intervals

Use broom::tidy() to compute exponentiated coefficients and CIs.

logistic_df <- NULL
  if (!is.null(model)) {
    coef_tidy <- tryCatch(
      broom::tidy(model, exponentiate = TRUE, conf.int = TRUE),
      error = function(e) NULL
    )
    if (!is.null(coef_tidy) && nrow(coef_tidy) > 0) {
      # Remove intercept, humanize predictor names
      coef_tidy <- coef_tidy[coef_tidy$term != "(Intercept)", ]

      # Humanize predictor names: convert dummy-coded factor levels
      # sex0 -> "Sex: 0", pclass2 -> "Class: 2", etc.
      predictor_names <- sapply(coef_tidy$term, function(term) {
        if (startsWith(term, "pclass")) {
          level <- substring(term, nchar("pclass") + 1)
          paste0("Class: ", level)
        } else if (startsWith(term, "sex")) {
          level <- substring(term, nchar("sex") + 1)
          paste0("Sex: ", level)
        } else if (startsWith(term, "embarked")) {
          level <- substring(term, nchar("embarked") + 1)
          paste0("Embarked: ", level)
        } else if (term == "age") {
          "Age"
        } else if (term == "family_size") {
          "Family Size"
        } else if (term == "fare") {
          "Fare"
        } else {
          gsub("_", " ", tools::toTitleCase(term))
        }
      }, USE.NAMES = FALSE)

      logistic_df <- data.frame(
        predictor = predictor_names,
        odds_ratio = coef_tidy$estimate,
        ci_lower = coef_tidy$conf.low,
        ci_upper = coef_tidy$conf.high,
        stringsAsFactors = FALSE
      )

      # Clamp extreme odds ratios to prevent chart scale issues
      logistic_df$odds_ratio <- pmin(pmax(logistic_df$odds_ratio, 0.1), 100)
      logistic_df$ci_lower <- pmin(pmax(logistic_df$ci_lower, 0.1), 100)
      logistic_df$ci_upper <- pmin(pmax(logistic_df$ci_upper, 0.1), 100)

      # Sort by absolute distance from 1 (most impactful first)
      logistic_df$distance_from_1 <- abs(log(logistic_df$odds_ratio))
      logistic_df <- logistic_df[order(logistic_df$distance_from_1, decreasing = TRUE), ]
      logistic_df$distance_from_1 <- NULL
      rownames(logistic_df) <- NULL

      # Count actual predictor terms in model (accounts for dummy-coded factors)
      n_pred_actual <- nrow(logistic_df)
    } else {
      n_pred_actual <- 0
    }
  } else {
    n_pred_actual <- 0
  }

Step 5: Survival Rate by Sex × Class Groups

Aggregate survival rate with sparse-bucket filter (n >= 5).

sex_labels <- c("0" = "Male", "1" = "Female")
  class_labels <- c("1" = "1st Class", "2" = "2nd Class", "3" = "3rd Class")

  survival_by_group <- df_clean %>%
    dplyr::group_by(sex, pclass) %>%
    dplyr::summarise(
      n = dplyr::n(),
      n_survived = sum(as.numeric(as.character(survived))),
      survival_rate = mean(as.numeric(as.character(survived))),
      .groups = "drop"
    ) %>%
    dplyr::filter(n >= 5) %>%
    dplyr::mutate(
      risk_group = paste0(sex_labels[as.character(sex)], " — ", class_labels[as.character(pclass)])
    ) %>%
    dplyr::select(risk_group, survival_rate, n, n_survived) %>%
    dplyr::arrange(dplyr::desc(survival_rate))

  survival_rate_by_group <- survival_by_group[, c("risk_group", "survival_rate")]

  # Validate survival_rate range (0-1)
  stopifnot(all(is.na(survival_rate_by_group$survival_rate) |
                (survival_rate_by_group$survival_rate >= 0 &
                 survival_rate_by_group$survival_rate <= 1)))

Step 6: Survival Rate by Passenger Class

Group by class only, apply sparse-bucket filter.

survival_by_class_df <- df_clean %>%
    dplyr::group_by(pclass) %>%
    dplyr::summarise(
      n = dplyr::n(),
      n_survived = sum(as.numeric(as.character(survived))),
      survival_rate = mean(as.numeric(as.character(survived))),
      .groups = "drop"
    ) %>%
    dplyr::filter(n >= 5) %>%
    dplyr::mutate(
      pclass_label = class_labels[as.character(pclass)]
    ) %>%
    dplyr::select(pclass_label, survival_rate, n, n_survived) %>%
    dplyr::arrange(factor(pclass_label, levels = c("1st Class", "2nd Class", "3rd Class")))

  survival_by_class <- survival_by_class_df[, c("pclass_label", "survival_rate")]

  # Validate range
  stopifnot(all(is.na(survival_by_class$survival_rate) |
                (survival_by_class$survival_rate >= 0 &
                 survival_by_class$survival_rate <= 1)))

Step 7: Age Distribution by Survival Status

Return raw age + survival status for box plot.

age_by_survival_df <- df_clean %>%
    dplyr::mutate(
      survival_status = ifelse(as.numeric(as.character(survived)) == 1, "Survived", "Did Not Survive")
    ) %>%
    dplyr::select(age, survival_status)

  age_by_survival <- age_by_survival_df

Step 8: Compute Summary Metrics

Overall statistics for tldr and overview.

overall_survival_rate <- mean(as.numeric(as.character(df_clean$survived)))
  overall_survival_pct <- overall_survival_rate * 100

  # Count passengers by key groups for tldr context
  n_female_1st <- nrow(df_clean[df_clean$sex == "1" & df_clean$pclass == "1", ])
  n_male_3rd <- nrow(df_clean[df_clean$sex == "0" & df_clean$pclass == "3", ])

  # Template variables for row counts (Rule T)
  tpl_vars <- list(
    n_customers = format(final_rows, big.mark = ","),
    n_rows_initial = format(initial_rows, big.mark = ","),
    n_rows_final = format(final_rows, big.mark = ","),
    pct_retention = sprintf("%.1f%%", 100 * final_rows / initial_rows)
  )

Step 9: Expose Results

Return named list with metrics, data frames, and template variables.

list(
    # LWS-74: row accounting
    initial_rows = initial_rows,
    final_rows = final_rows,
    rows_removed = rows_removed,
    filters_applied = "Removed rows with missing age values",

    # Metrics for tldr + overview
    metrics = list(
      n_observations = final_rows,
      overall_survival_rate = overall_survival_rate,
      overall_survival_pct = overall_survival_pct,
      n_predictors = n_pred_actual,
      model_p_value = if (!is.null(model)) {
        # Approximate p-value from model deviance
        1 - pchisq(model$null.deviance - model$deviance,
                   model$df.null - model$df.residual)
      } else NA_real_
    ),

    # Derived data frames for card plotting
    survival_rate_by_group = survival_rate_by_group,
    survival_by_class = survival_by_class,
    age_by_survival = age_by_survival,

    # Logistic regression results
    logistic_drivers = logistic_df,
    model = model,
    model_summary = list(
      formula = "survived ~ pclass + sex + age + family_size + fare + embarked",
      type = "Logistic Regression",
      n_predictors = n_pred_actual,
      n_observations = final_rows,
      overall_survival_rate = overall_survival_rate
    ),

    # LAT-349: datasets with primary dataset info + target distribution (classification)
    datasets = list(
      primary = list(
        n_rows = final_rows,
        target_col = "survived",
        class_distribution = as.list(table(df_clean$survived))
      )
    ),

    # Template variables for Rule T (size strings)
    tpl_vars = tpl_vars,

    # Raw data for cards needing detailed inspection
    df_clean = df_clean,
    survival_by_group_details = survival_by_group,
    survival_by_class_details = survival_by_class_df
  )
}

Compute shared resources

shared <- compute_shared(df, params)

Finalize (do not modify)

Your data has more stories to tell. Run any analysis on your own data — validated R modules, interactive reports, AI insights, and PDF export. 500 free credits on signup.
Try Free — No Signup Sign Up Free

Report an Issue

Tell us what's wrong. You'll get a free re-run of this analysis so you can try again with different parameters. If the re-run still doesn't meet your expectations, we'll refund your credits.

Want to run this analysis on your own data? Upload CSV — Free Analysis See Pricing