Executive Summary

This report presents an exploratory and predictive analysis of inpatient hospital care patterns using a dataset of 55,500 patient records spanning May 2019 through May 2024. The analysis examines length of stay, billing patterns, admission characteristics, and test result outcomes across six medical conditions, three admission types, and five insurance providers.

Key findings:

  • Average length of stay was 15.5 days across all admission types and medical conditions, with remarkably consistent distributions suggesting stable operational patterns across the patient population.

  • Billing amounts averaged $25,539 per admission with no material difference across insurance providers, indicating a balanced payer mix with no disproportionate revenue dependency on any single insurer.

  • Test results were evenly distributed across Normal (33.4%), Abnormal (33.6%), and Inconclusive (33.0%) outcomes — an unusual uniformity that informed the interpretation of predictive modeling results.

  • Cancer patients admitted via emergency represented the longest average length of stay at 16 days, the only condition-admission type combination to meaningfully exceed the overall average.

  • Predictive modeling confirmed that patient-level variables available in this dataset do not independently predict length of stay or test result outcomes, a finding consistent with synthetically generated data. Recommendations for extending this analysis with real clinical data are provided in the final section.


Background & Objectives

The Analytical Question

Hospital administrators and clinical operations teams routinely monitor a core set of performance indicators to manage capacity, control costs, and improve patient outcomes. Among the most closely watched are length of stay (LOS) and 30-day readmission rates — both publicly reported by the Centers for Medicare and Medicaid Services (CMS) and directly tied to hospital reimbursement under the Hospital Readmissions Reduction Program (HRRP).

This analysis asks three questions relevant to that operational context:

  1. What patient and admission characteristics are associated with longer lengths of stay?
  2. Do billing patterns vary meaningfully across insurance providers and admission types?
  3. Can patient-level variables predict abnormal test results or extended stays?

Dataset Overview

The dataset used in this analysis is a structured patient records dataset containing 55,500 inpatient encounters sourced from Kaggle (Prasad, 2024). It includes demographic information, admission and discharge dates, medical conditions, medications, insurance provider, billing amounts, and test results.

# Dataset dimensions and date range
tibble(
  Metric = c("Total Records", "Variables", "Date Range", 
             "Medical Conditions", "Insurance Providers",
             "Admission Types"),
  Value  = c(
    format(nrow(df_clean), big.mark = ","),
    ncol(df_clean),
    paste(min(df_clean$date_of_admission), "to",
          max(df_clean$date_of_admission)),
    nlevels(df_clean$medical_condition),
    nlevels(df_clean$insurance_provider),
    nlevels(df_clean$admission_type)
  )
) %>%
  kable(
    format  = "html",
    caption = "Table 1: Dataset Overview",
    align   = c("l", "l")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover"),
    full_width        = FALSE,
    position          = "left"
  ) %>%
  column_spec(1, bold = TRUE) %>%
  row_spec(0, background = "#2C3E50", color = "white")
Table 1: Dataset Overview
Metric Value
Total Records 55,500
Variables 21
Date Range 2019-05-08 to 2024-05-07
Medical Conditions 6
Insurance Providers 5
Admission Types 3

A note on data provenance: The uniformity observed throughout this analysis is consistent with synthetically generated data, where outcomes are sampled independently of patient characteristics rather than reflecting true clinical relationships. Findings should be interpreted in this context. Section 7 discusses how this analysis would extend to real administrative data sources such as HCUP or institutional EHR exports. —


Data Preparation

Approach

Rather than applying cleaning operations individually to each variable — a common but brittle approach — this analysis implements a reusable cleaning function applied systematically across the dataset. This design means the same cleaning logic can be applied to new data files, additional monthly extracts, or department-level subsets without rewriting any code. The full cleaning function is available in the project GitHub repository.

Cleaning Operations

The following operations were applied to produce the analytical dataset:

tibble(
  Operation = c(
    "Column name standardization",
    "Name capitalization correction",
    "Date parsing",
    "Length of stay calculation",
    "Admission month extraction",
    "Admission day of week extraction",
    "Age group classification",
    "Billing tier classification",
    "Data quality flagging",
    "Factor conversion"
  ),
  Detail = c(
    "Snake_case applied via janitor::clean_names()",
    "Inconsistent mixed case corrected to title case",
    "Date.of.Admission and Discharge.Date parsed from character to Date type",
    "Derived as integer difference between discharge and admission dates",
    "Extracted from admission date as ordered factor (Jan–Dec)",
    "Extracted from admission date as ordered factor (Monday–Sunday)",
    "Binned into Pediatric, Young Adult, Middle Aged, Senior",
    "Binned into Low (<$15K), Medium ($15K–$35K), High (>$35K)",
    "108 records with negative billing amounts flagged and excluded from cost analysis",
    "Nine categorical variables converted from character to factor type"
  )
) %>%
  kable(
    format  = "html",
    caption = "Table 2: Data Preparation Operations",
    align   = c("l", "l")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width        = TRUE
  ) %>%
  column_spec(1, bold = TRUE, width = "30%") %>%
  column_spec(2, width = "70%") %>%
  row_spec(0, background = "#2C3E50", color = "white")
Table 2: Data Preparation Operations
Operation Detail
Column name standardization Snake_case applied via janitor::clean_names()
Name capitalization correction Inconsistent mixed case corrected to title case
Date parsing Date.of.Admission and Discharge.Date parsed from character to Date type
Length of stay calculation Derived as integer difference between discharge and admission dates
Admission month extraction Extracted from admission date as ordered factor (Jan–Dec)
Admission day of week extraction Extracted from admission date as ordered factor (Monday–Sunday)
Age group classification Binned into Pediatric, Young Adult, Middle Aged, Senior
Billing tier classification Binned into Low (<$15K), Medium ($15K–$35K), High (>$35K)
Data quality flagging 108 records with negative billing amounts flagged and excluded from cost analysis
Factor conversion Nine categorical variables converted from character to factor type

Data Quality

# Missing value assessment
missing_summary <- df_clean %>%
  summarise(across(everything(), ~ sum(is.na(.)))) %>%
  pivot_longer(everything(),
               names_to  = "Column",
               values_to = "Missing Values")

# Data quality flag summary
quality_summary <- df_clean %>%
  count(data_quality_flag) %>%
  mutate(
    Percentage = round(n / nrow(df_clean) * 100, 2)
  ) %>%
  rename(
    "Flag"    = data_quality_flag,
    "Records" = n,
    "% of Total" = Percentage
  )

quality_summary %>%
  kable(
    format  = "html",
    caption = "Table 3: Data Quality Flag Summary",
    align   = c("l", "r", "r")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover"),
    full_width        = FALSE,
    position          = "left"
  ) %>%
  column_spec(1, bold = TRUE) %>%
  row_spec(0, background = "#2C3E50", color = "white") %>%
  row_spec(1, color = "#27AE60") %>%   # green for OK records
  row_spec(2, color = "#E74C3C")       # red for flagged records
Table 3: Data Quality Flag Summary
Flag Records % of Total
Negative billing — exclude from cost analysis 108 0.19
OK 55392 99.81

Data quality note: 108 records (0.19% of the dataset) carried negative billing amounts with no clinical explanation. These records were flagged rather than silently removed, preserving the audit trail while excluding them from all cost-related analysis. All 55,500 records are retained in the master dataset for non-billing analyses.

Engineered Features

Five analytical variables were derived from existing fields to support downstream exploration and modeling:

tibble(
  `New Variable`  = c("length_of_stay", "admission_month",
                       "admission_day_of_week", "age_group",
                       "billing_tier"),
  `Derived From`  = c("discharge_date − date_of_admission",
                       "date_of_admission",
                       "date_of_admission",
                       "age",
                       "billing_amount"),
  `Type`          = c("Integer", "Ordered factor",
                       "Ordered factor", "Factor", "Factor"),
  `Purpose`       = c("Primary outcome variable for LOS analysis",
                       "Seasonal pattern detection",
                       "Admission timing analysis",
                       "Demographic segmentation",
                       "Cost tier segmentation")
) %>%
  kable(
    format  = "html",
    caption = "Table 4: Engineered Features",
    align   = c("l", "l", "l", "l")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width        = TRUE
  ) %>%
  column_spec(1, bold = TRUE, color = "#2C3E50") %>%
  row_spec(0, background = "#2C3E50", color = "white")
Table 4: Engineered Features
New Variable Derived From Type Purpose
length_of_stay discharge_date − date_of_admission Integer Primary outcome variable for LOS analysis
admission_month date_of_admission Ordered factor Seasonal pattern detection
admission_day_of_week date_of_admission Ordered factor Admission timing analysis
age_group age Factor Demographic segmentation
billing_tier billing_amount Factor Cost tier segmentation

Exploratory Findings

Exploratory analysis examined four dimensions of the patient population: length of stay, billing patterns, admission volume over time, and the interaction between medical condition and admission type. Visualizations were developed using a consistent custom theme to support readability across all charts.

Length of Stay by Medical Condition

Length of stay is one of the most closely monitored operational metrics in hospital administration, directly influencing staffing requirements, bed capacity planning, and reimbursement under prospective payment systems.

ggplot(df_clean, 
       aes(x    = reorder(medical_condition, length_of_stay, median),
           y    = length_of_stay,
           fill = medical_condition)) +
  geom_boxplot(alpha = 0.8, outlier.size = 1, outlier.alpha = 0.3) +
  scale_fill_manual(values = condition_colors) +
  coord_flip() +
  labs(
    title    = "Length of Stay by Medical Condition",
    subtitle = "Median LOS is similar across conditions — distributions reveal the real variation",
    x        = NULL,
    y        = "Length of Stay (Days)",
    caption  = "Source: Kaggle Healthcare Dataset | n = 55,500"
  ) +
  theme_hospital() +
  theme(legend.position = "none")
Figure 1: Length of Stay Distribution by Medical Condition

Figure 1: Length of Stay Distribution by Medical Condition

LOS distributions were broadly consistent across all six medical conditions, with medians ranging from approximately 14 to 16 days. The interquartile ranges overlapped substantially across conditions, suggesting that diagnosis alone is not a strong determinant of inpatient stay duration in this dataset. In a real clinical environment, procedure complexity, comorbidity burden, and discharge disposition would be expected to introduce greater condition-level variation.

Billing Distribution by Insurance Provider

Understanding billing patterns across payers is a core function of hospital financial analytics, informing contract negotiations, revenue cycle management, and payer mix strategy.

ggplot(df_billing, 
       aes(x    = reorder(insurance_provider, billing_amount, median),
           y    = billing_amount,
           fill = insurance_provider)) +
  geom_violin(alpha = 0.7, color = NA) +
  geom_boxplot(width = 0.15, fill = "white", color = "#333333",
               outlier.size = 0.8, outlier.alpha = 0.3) +
  scale_fill_brewer(palette = "Set2") +
  scale_y_continuous(labels = dollar_format()) +
  coord_flip() +
  labs(
    title    = "Billing Amount Distribution by Insurance Provider",
    subtitle = "Violin width shows frequency — wider = more patients at that billing level",
    x        = NULL,
    y        = "Billing Amount (USD)",
    caption  = "Source: Kaggle Healthcare Dataset | Negative billing values excluded (n = 108)"
  ) +
  theme_hospital() +
  theme(legend.position = "none")
Figure 2: Billing Amount Distribution by Insurance Provider

Figure 2: Billing Amount Distribution by Insurance Provider

Billing distributions were remarkably consistent across all five insurance providers, with median amounts clustering around $25,500 regardless of payer. This uniformity indicates a balanced payer mix with no disproportionate revenue concentration — a favorable characteristic from a financial stability standpoint. It also suggests that reimbursement rates do not vary materially by insurer in this dataset, which would be unusual in real administrative data where Medicare and Medicaid typically reimburse at lower rates than commercial insurers.

Monthly Admission Volume

Seasonal admission patterns inform staffing models, supply chain planning, and capacity management decisions across hospital operations teams.

df_clean %>%
  count(admission_month, admission_type) %>%
  ggplot(aes(x     = admission_month,
             y     = n,
             color = admission_type,
             group = admission_type)) +
  geom_line(linewidth = 1.1) +
  geom_point(size = 2.5) +
  scale_color_manual(values = hospital_colors) +
  scale_y_continuous(limits = c(0, NA)) +
  facet_wrap(~ admission_type, ncol = 1) +
  labs(
    title    = "Monthly Admission Volume by Admission Type",
    subtitle = "Examining seasonal patterns across Elective, Emergency, and Urgent admissions",
    x        = NULL,
    y        = "Number of Admissions",
    caption  = "Source: Kaggle Healthcare Dataset"
  ) +
  theme_hospital() +
  theme(legend.position = "none")
Figure 3: Monthly Admission Volume by Admission Type

Figure 3: Monthly Admission Volume by Admission Type

Admission volumes showed no clinically meaningful seasonal pattern across any of the three admission types. Elective, Emergency, and Urgent admissions all remained stable throughout the year with minor month-to-month fluctuation attributable to normal statistical variation. In real hospital data, emergency admissions typically spike during winter months due to respiratory illness seasonality, and elective admissions often dip in summer and major holiday periods. The absence of these patterns further supports the synthetic nature of the dataset.

Condition and Admission Type Interaction

Single-variable analysis revealed limited variation in LOS. Examining the interaction between medical condition and admission type provides a more granular view of where stay duration differences emerge.

df_clean %>%
  group_by(medical_condition, admission_type) %>%
  summarise(avg_los = round(mean(length_of_stay), 1), 
            .groups = "drop") %>%
  ggplot(aes(x    = admission_type,
             y    = medical_condition,
             fill = avg_los)) +
  geom_tile(color = "white", linewidth = 0.8) +
  geom_text(aes(label = avg_los), size = 4, fontface = "bold",
            color = "white") +
  scale_fill_gradient(low  = "#D6E4F0",
                      high = "#1A5276",
                      name = "Avg LOS (Days)") +
  labs(
    title    = "Average Length of Stay by Condition and Admission Type",
    subtitle = "Darker blue = longer average stay",
    x        = "Admission Type",
    y        = NULL,
    caption  = "Source: Kaggle Healthcare Dataset"
  ) +
  theme_hospital() +
  theme(
    panel.grid = element_blank(),
    legend.position = "right"
  )
Figure 4: Average LOS by Condition and Admission Type

Figure 4: Average LOS by Condition and Admission Type

The heatmap reveals that the overall range of average LOS across all condition-admission type combinations spans only 15.1 to 16.0 days — a spread of less than one day across 18 distinct patient subgroups. Cancer patients admitted via emergency represented the longest average stay at 16.0 days, while diabetic patients admitted for urgent care showed the shortest at 15.1 days. The narrow range confirmed that no single combination of condition and admission type drives meaningfully differentiated LOS outcomes, motivating the multivariate modeling approach described in the following section. —

Predictive Modeling

Approach

Exploratory analysis consistently revealed narrow distributions across patient subgroups, suggesting that no single variable meaningfully differentiates patient outcomes in this dataset. Two supervised learning models were developed using the tidymodels framework to formally test whether combinations of patient-level variables could predict length of stay or test result outcomes.

Both models followed the same structured workflow:

  • Split: Data divided into 80% training and 20% held-out test sets using stratified sampling to preserve outcome distributions across both sets
  • Recipe: Preprocessing pipeline defining the model formula, dummy variable encoding, zero-variance filtering, and numeric normalization
  • Specification: Model type and computational engine defined independently of the recipe, enabling straightforward model substitution
  • Workflow: Recipe and specification bundled and fitted to training data only
  • Evaluation: Performance assessed exclusively on the held-out test set to prevent optimistic bias from training set evaluation
# Reproducible train/test split
set.seed(42)

df_model <- df_clean %>%
  filter(data_quality_flag == "OK") %>%
  mutate(
    abnormal_test = factor(
      if_else(test_results == "Abnormal", "Abnormal", "Not_Abnormal"),
      levels = c("Not_Abnormal", "Abnormal")
    )
  ) %>%
  select(length_of_stay, abnormal_test, age, gender,
         medical_condition, admission_type, insurance_provider,
         billing_tier, age_group)

hospital_split <- initial_split(df_model, prop = 0.80, 
                                strata = length_of_stay)
hospital_train <- training(hospital_split)
hospital_test  <- testing(hospital_split)

# Display split summary
tibble(
  Dataset    = c("Training Set", "Test Set", "Total"),
  Records    = c(nrow(hospital_train), 
                 nrow(hospital_test),
                 nrow(hospital_train) + nrow(hospital_test)),
  Proportion = c("80%", "20%", "100%")
) %>%
  kable(
    format  = "html",
    caption = "Table 5: Train/Test Split Summary",
    align   = c("l", "r", "r")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover"),
    full_width        = FALSE,
    position          = "left"
  ) %>%
  column_spec(1, bold = TRUE) %>%
  row_spec(0, background = "#2C3E50", color = "white") %>%
  row_spec(3, bold = TRUE, background = "#F5F5F5")
Table 5: Train/Test Split Summary
Dataset Records Proportion
Training Set 44312 80%
Test Set 11080 20%
Total 55392 100%

Model 1: Linear Regression — Predicting Length of Stay

# Recipe
los_recipe <- recipe(
    length_of_stay ~ age + gender + medical_condition +
                     admission_type + insurance_provider +
                     billing_tier + age_group,
    data = hospital_train
  ) %>%
  step_dummy(all_nominal_predictors(), one_hot = FALSE) %>%
  step_zv(all_predictors()) %>%
  step_normalize(all_numeric_predictors())

# Specification
los_spec <- linear_reg() %>%
  set_engine("lm") %>%
  set_mode("regression")

# Workflow and fit
los_fit <- workflow() %>%
  add_recipe(los_recipe) %>%
  add_model(los_spec) %>%
  fit(data = hospital_train)

# Predictions on test set
los_predictions <- los_fit %>%
  predict(hospital_test) %>%
  bind_cols(hospital_test %>%
              select(length_of_stay, medical_condition,
                     admission_type, age_group))

# Metrics
los_metrics <- los_predictions %>%
  metrics(truth = length_of_stay, estimate = .pred)

los_metrics %>%
  filter(.metric %in% c("rmse", "rsq", "mae")) %>%
  mutate(
    Metric = case_when(
      .metric == "rmse" ~ "RMSE (Days)",
      .metric == "rsq"  ~ "R-Squared",
      .metric == "mae"  ~ "MAE (Days)"
    ),
    Value = case_when(
      .metric == "rsq"  ~ round(.estimate, 6),
      TRUE              ~ round(.estimate, 2)
    )
  ) %>%
  select(Metric, Value) %>%
  kable(
    format  = "html",
    caption = "Table 6: Linear Regression Performance Metrics",
    align   = c("l", "r")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover"),
    full_width        = FALSE,
    position          = "left"
  ) %>%
  column_spec(1, bold = TRUE) %>%
  row_spec(0, background = "#2C3E50", color = "white")
Table 6: Linear Regression Performance Metrics
Metric Value
RMSE (Days) 8.650000
R-Squared 0.000052
MAE (Days) 7.510000
ggplot(los_predictions, aes(x = length_of_stay, y = .pred)) +
  geom_point(alpha = 0.15, size = 1.2, color = "#2C3E50") +
  geom_abline(slope = 1, intercept = 0,
              color     = "#E74C3C",
              linewidth = 1,
              linetype  = "dashed") +
  scale_x_continuous(limits = c(0, 35)) +
  scale_y_continuous(limits = c(0, 35)) +
  labs(
    title    = "Predicted vs Actual Length of Stay",
    subtitle = "Points falling on the red line indicate perfect prediction",
    x        = "Actual LOS (Days)",
    y        = "Predicted LOS (Days)",
    caption  = "Evaluated on held-out test set (n = 11,080)"
  ) +
  theme_hospital()
Figure 5: Predicted vs Actual Length of Stay

Figure 5: Predicted vs Actual Length of Stay

The linear regression model yielded an R² of approximately 0.00 and an RMSE of 8.65 days, indicating that the selected patient-level variables do not meaningfully predict length of stay. The predicted vs actual plot confirms this — rather than tracking the diagonal reference line, predictions cluster in a narrow horizontal band centered near the dataset mean of 15.5 days. The model is effectively predicting the population average for every patient regardless of their characteristics, which is the expected behavior when predictors carry no true signal.

Model 2: Logistic Regression — Predicting Abnormal Test Results

# Recipe
logistic_recipe <- recipe(
    abnormal_test ~ age + gender + medical_condition +
                    admission_type + insurance_provider +
                    billing_tier + age_group + length_of_stay,
    data = hospital_train
  ) %>%
  step_dummy(all_nominal_predictors(), one_hot = FALSE) %>%
  step_zv(all_predictors()) %>%
  step_normalize(all_numeric_predictors())

# Specification
logistic_spec <- logistic_reg() %>%
  set_engine("glm") %>%
  set_mode("classification")

# Workflow and fit
logistic_fit <- workflow() %>%
  add_recipe(logistic_recipe) %>%
  add_model(logistic_spec) %>%
  fit(data = hospital_train)

# Predictions
logistic_predictions <- logistic_fit %>%
  predict(hospital_test) %>%
  bind_cols(
    predict(logistic_fit, hospital_test, type = "prob"),
    hospital_test %>%
      select(abnormal_test, medical_condition,
             admission_type, age_group)
  )

# Confusion matrix
conf_mat_result <- logistic_predictions %>%
  conf_mat(truth = abnormal_test, estimate = .pred_class)

# Metrics table
logistic_predictions %>%
  metrics(truth    = abnormal_test,
          estimate = .pred_class) %>%
  mutate(
    Metric = case_when(
      .metric == "accuracy" ~ "Accuracy",
      .metric == "kap"      ~ "Kappa",
      TRUE                  ~ .metric
    ),
    Value = round(.estimate, 4)
  ) %>%
  select(Metric, Value) %>%
  kable(
    format  = "html",
    caption = "Table 7: Logistic Regression Performance Metrics",
    align   = c("l", "r")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover"),
    full_width        = FALSE,
    position          = "left"
  ) %>%
  column_spec(1, bold = TRUE) %>%
  row_spec(0, background = "#2C3E50", color = "white")
Table 7: Logistic Regression Performance Metrics
Metric Value
Accuracy 0.6649
Kappa 0.0000
conf_mat_result %>%
  autoplot(type = "mosaic") +
  labs(
    title    = "Confusion Matrix — Abnormal Test Result Prediction",
    subtitle = "Rows = Actual | Columns = Predicted",
    caption  = "Evaluated on held-out test set (n = 11,080)"
  ) +
  theme_hospital()
Figure 6: Confusion Matrix — Abnormal Test Result Prediction

Figure 6: Confusion Matrix — Abnormal Test Result Prediction

The logistic regression model achieved an accuracy of 66.5% with a Kappa of 0.00 — confirming it added no predictive value beyond naively predicting the majority class for every observation. The confusion matrix shows the model predicted “Not Abnormal” for all 11,080 test records, never once classifying a patient as having an abnormal result. This is known as the null classifier or majority class problem: when no learnable signal exists in the predictors, a classification model defaults to always predicting the most frequent outcome.

Modeling interpretation: An accuracy of 66.5% sounds reasonable in isolation but is entirely meaningless here — the same score is achievable by predicting “Not Abnormal” for every patient with a single line of code. Kappa, which measures accuracy beyond what chance alone would produce, is the more honest metric and its value of 0.00 tells the complete story. Reporting null results transparently is standard practice in rigorous analytical work — a model that appears to perform well on synthetic data by overfitting to noise would produce dangerously misleading conclusions if applied to real patient populations. —

Limitations

Data Provenance

The most significant limitation of this analysis is the synthetic nature of the underlying dataset. Several characteristics of the data are inconsistent with real administrative hospital data and should be understood before drawing operational conclusions:

tibble(
  Limitation = c(
    "Uniform outcome distributions",
    "No payer reimbursement differential",
    "Absent seasonal admission patterns",
    "No comorbidity information",
    "No procedure or ICD-10 codes",
    "No readmission data",
    "Single facility implied",
    "No discharge disposition detail"
  ),
  `Observed Pattern` = c(
    "Test results split evenly ~33% across three categories",
    "Billing amounts nearly identical across all five insurers",
    "Admission volume flat across all twelve months",
    "No comorbidity scores or secondary diagnoses available",
    "Medical conditions represented as free text labels only",
    "No 30-day readmission flag or subsequent encounter data",
    "No facility identifier — cross-site comparison not possible",
    "Discharge to home, rehab, or skilled nursing not recorded"
  ),
  `Real Data Expectation` = c(
    "Abnormal results concentrated in specific conditions and age groups",
    "Medicare and Medicaid typically reimburse at 60–80% of commercial rates",
    "Emergency admissions spike in winter; elective dips in holiday periods",
    "Elixhauser or Charlson comorbidity indices materially predict LOS",
    "ICD-10 diagnosis and procedure codes enable granular clinical grouping",
    "Readmission rates are publicly reported CMS quality metrics",
    "Multi-facility data enables benchmarking and site-level variation analysis",
    "Discharge disposition is a strong predictor of readmission risk"
  )
) %>%
  kable(
    format  = "html",
    caption = "Table 8: Dataset Limitations and Real-World Expectations",
    align   = c("l", "l", "l")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width        = TRUE
  ) %>%
  column_spec(1, bold = TRUE, width = "25%") %>%
  column_spec(2, width = "35%") %>%
  column_spec(3, width = "40%") %>%
  row_spec(0, background = "#2C3E50", color = "white")
Table 8: Dataset Limitations and Real-World Expectations
Limitation Observed Pattern Real Data Expectation
Uniform outcome distributions Test results split evenly ~33% across three categories Abnormal results concentrated in specific conditions and age groups
No payer reimbursement differential Billing amounts nearly identical across all five insurers Medicare and Medicaid typically reimburse at 60–80% of commercial rates
Absent seasonal admission patterns Admission volume flat across all twelve months Emergency admissions spike in winter; elective dips in holiday periods
No comorbidity information No comorbidity scores or secondary diagnoses available Elixhauser or Charlson comorbidity indices materially predict LOS
No procedure or ICD-10 codes Medical conditions represented as free text labels only ICD-10 diagnosis and procedure codes enable granular clinical grouping
No readmission data No 30-day readmission flag or subsequent encounter data Readmission rates are publicly reported CMS quality metrics
Single facility implied No facility identifier — cross-site comparison not possible Multi-facility data enables benchmarking and site-level variation analysis
No discharge disposition detail Discharge to home, rehab, or skilled nursing not recorded Discharge disposition is a strong predictor of readmission risk

Modeling Constraints

Beyond data provenance, two methodological limitations apply to the modeling section specifically:

Feature availability: The predictors available in this dataset — demographics, admission type, insurance provider, and medical condition label — represent a minimal feature set relative to what clinical decision support models typically incorporate. Real LOS prediction models in production hospital environments commonly include procedure complexity scores, prior utilization history, social determinants of health, and real-time clinical measurements.

Model scope: This analysis evaluated linear and logistic regression as interpretable baseline models appropriate for an administrative data context. More flexible approaches such as gradient boosting or random forests were not pursued, as added model complexity is unlikely to recover signal that does not exist in the underlying data. In a real clinical dataset with richer features, ensemble methods would be a natural extension of this baseline work.


Recommendations

For Hospital Analytics Teams

The analytical framework developed here — a reusable cleaning pipeline, consistent visualization theme, and structured modeling workflow — translates directly to real administrative data. The following extensions are recommended when applying this approach to institutional data sources:

tibble(
  Priority = c("High", "High", "Medium", "Medium", "Low"),
  Recommendation = c(
    "Incorporate ICD-10 diagnosis and procedure codes",
    "Add 30-day readmission flag as a modeling outcome",
    "Integrate Elixhauser comorbidity index scores",
    "Expand to multi-facility data for benchmarking",
    "Explore ensemble modeling methods"
  ),
  Rationale = c(
    "Enables clinical grouping via DRG classification, materially improving 
     LOS prediction and condition-level analysis",
    "Readmission is a CMS-reported quality metric with direct reimbursement 
     implications under the Hospital Readmissions Reduction Program",
    "Comorbidity burden is consistently among the strongest predictors of 
     LOS and readmission risk in published clinical literature",
    "Cross-site variation analysis enables identification of high-performing 
     facilities and dissemination of best practices",
    "Gradient boosting and random forest models are well-suited to the 
     high-dimensional feature spaces produced by ICD-10 coding"
  )
) %>%
  kable(
    format  = "html",
    caption = "Table 9: Recommendations for Extension to Real Clinical Data",
    align   = c("l", "l", "l")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width        = TRUE
  ) %>%
  column_spec(1, bold = TRUE, width = "10%",
              color = if_else(c(TRUE, TRUE, FALSE, FALSE, FALSE),
                              "#E74C3C", "#F39C12")) %>%
  column_spec(2, bold = TRUE, width = "35%") %>%
  column_spec(3, width = "55%") %>%
  row_spec(0, background = "#2C3E50", color = "white")
Table 9: Recommendations for Extension to Real Clinical Data
Priority Recommendation Rationale
High Incorporate ICD-10 diagnosis and procedure codes Enables clinical grouping via DRG classification, materially improving LOS prediction and condition-level analysis
High Add 30-day readmission flag as a modeling outcome Readmission is a CMS-reported quality metric with direct reimbursement implications under the Hospital Readmissions Reduction Program
Medium Integrate Elixhauser comorbidity index scores Comorbidity burden is consistently among the strongest predictors of LOS and readmission risk in published clinical literature
Medium Expand to multi-facility data for benchmarking Cross-site variation analysis enables identification of high-performing facilities and dissemination of best practices
Low Explore ensemble modeling methods Gradient boosting and random forest models are well-suited to the high-dimensional feature spaces produced by ICD-10 coding

For This Analysis Specifically

Three immediate next steps would meaningfully strengthen this work:

1. Apply to HCUP data. The Healthcare Cost and Utilization Project provides de-identified inpatient administrative data with ICD-10 codes, DRG classifications, comorbidity flags, and discharge disposition for millions of encounters annually. The cleaning function and modeling workflow developed here require minimal modification to operate on HCUP data structures.

2. Add a readmission outcome variable. Linking index admissions to subsequent encounters within a 30-day window would enable modeling of the metric most directly tied to hospital reimbursement under current CMS payment programs.

3. Incorporate geospatial analysis. Hospital zip code data, where available, enables analysis of social determinants of health at the census tract level — an increasingly important dimension of population health management and a growing area of hospital analytics practice.


Technical Appendix

Environment

tibble(
  Component = c("R Version", "Primary Packages", 
                "Modeling Framework", "Report Format"),
  Detail    = c(
    paste(R.version$major, R.version$minor, sep = "."),
    "tidyverse 2.0, janitor, skimr, kableExtra",
    "tidymodels",
    "R Markdown — HTML with floating TOC and code folding"
  )
) %>%
  kable(
    format  = "html",
    caption = "Table 10: Technical Environment",
    align   = c("l", "l")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover"),
    full_width        = FALSE,
    position          = "left"
  ) %>%
  column_spec(1, bold = TRUE) %>%
  row_spec(0, background = "#2C3E50", color = "white")
Table 10: Technical Environment
Component Detail
R Version 4.5.3
Primary Packages tidyverse 2.0, janitor, skimr, kableExtra
Modeling Framework tidymodels
Report Format R Markdown — HTML with floating TOC and code folding

Data Source

Prasad, R. (2024). Healthcare Dataset [Data set]. Kaggle. https://www.kaggle.com/datasets/prasad22/healthcare-dataset

Repository

Full source code, including the data cleaning function, exploratory analysis scripts, and this report, is available at: github.com/brianketchens/hospital-analysis


Analysis conducted in R 4.5.3 | Report generated April 16, 2026