Reading and Loading Data

Topics: read, import, readr, csv

In medical research and hospital informatics, data often comes from electronic health records (EHRs), lab systems, or survey results — usually in formats like CSV, Excel, or JSON.

This section will teach you how to load these files into R, so you can begin analyzing them effectively.

Reading CSV Files

CSV (Comma-Separated Values) is one of the most common formats for exporting medical data — from patient logs to laboratory results.

To try the codes below, download the patients_data.csv file here and save (or move) it in your current working directory.

library(readr)
library(dplyr)

# Load clinical data
df <- read_csv("patients_data.csv")
print(head(df))

Common Parameters When Reading CSV Files

df_csv <- read_csv(
  "patients_data.csv",
  col_names    = TRUE,             # First row as column names
  na           = c("N/A", "NULL"), # Treat these as missing
  col_types    = cols(
    Admission_Date = col_date(format = "%Y-%m-%d")
  )
)

Creating and Saving Sample Medical Data

Let’s create a simple medical dataset that simulates patient information:

set.seed(42)
n <- 100

sample_data <- data.frame(
  Patient_ID     = 1:n,
  Name           = paste0("Patient_", 1:n),
  Age            = sample(18:80, n, replace = TRUE),
  Diagnosis      = sample(c("Diabetes", "Hypertension", "Healthy"), n, replace = TRUE),
  Blood_Pressure = sample(100:180, n, replace = TRUE),
  Glucose        = sample(70:200,  n, replace = TRUE),
  Admission_Date = seq.Date(as.Date("2024-01-01"), by = "week", length.out = n)
)

# Save sample data
write_csv(sample_data, "sample_patients.csv")
cat("Sample patient data created and saved!\n")
Reading Other File Formats

Medical data can also come in Excel, JSON, or even databases.

Reading Excel files

library(readxl)
df_excel <- read_excel("data.xlsx", sheet = "Sheet1")

Working with Our Sample CSV

df <- read_csv("sample_patients.csv")
df$Admission_Date <- as.Date(df$Admission_Date)

cat("Data loaded successfully!\n")
print(head(df))

Here is our data:

# A tibble: 6 × 7
  Patient_ID Name        Age Diagnosis    Blood_Pressure Glucose Admission_Date
       <dbl> <chr>     <dbl> <chr>                 <dbl>   <dbl> <date>        
1          1 Patient_1    66 Diabetes                153     120 2024-01-01    
2          2 Patient_2    54 Hypertension            130      79 2024-01-08    
3          3 Patient_3    18 Diabetes                142     190 2024-01-15    
4          4 Patient_4    42 Hypertension            151     158 2024-01-22    
5          5 Patient_5    27 Healthy                 180     106 2024-01-29    
6          6 Patient_6    53 Diabetes                158      96 2024-02-05  

The <dbl>, <chr>, and <date> just below the character names tells us the data types of the variables we are working with.

  • <dbl> indicates double, e.g., class(66)
  • <chr> indicates character (string)
  • <date> indicates double (date)
  • <int> indicates integer (date), e.g., class(66L)

The result is of class tibble, which is a modern, optimized reimagining of the traditional data frame (data.frame) in R. Here, the data types are determined using typeof() instead of class() which are synonymous.

Handling Missing Data

Missing data is very common in medical research — for example, when a lab result isn’t recorded or a patient skips a follow-up visit.

Let’s simulate and handle missing values:

library(dplyr)

df_with_missing                  <- df
df_with_missing$Glucose  [6:11]  <- NA
df_with_missing$Diagnosis[16:21] <- NA

cat("Missing data summary:\n")
print(colSums(is.na(df_with_missing)))

The result:

Missing data summary:
    Patient_ID  Name  Age  Diagnosis Blood_Pressure  Glucose Admission_Date 
             0     0    0          6              0        6              0 

Here’s the new data frame with missing values:

print(df_with_missing, n = 30)
# A tibble: 100 × 7
   Patient_ID Name         Age Diagnosis    Blood_Pressure Glucose Admission_Date
        <dbl> <chr>      <dbl> <chr>                 <dbl>   <dbl> <date>        
 1          1 Patient_1     66 Diabetes                153     120 2024-01-01    
 2          2 Patient_2     54 Hypertension            130      79 2024-01-08    
 3          3 Patient_3     18 Diabetes                142     190 2024-01-15    
 4          4 Patient_4     42 Hypertension            151     158 2024-01-22    
 5          5 Patient_5     27 Healthy                 180     106 2024-01-29    
 6          6 Patient_6     53 Diabetes                158      NA 2024-02-05    
 7          7 Patient_7     35 Diabetes                126      NA 2024-02-12    
 8          8 Patient_8     75 Healthy                 129      NA 2024-02-19    
 9          9 Patient_9     66 Hypertension            151      NA 2024-02-26    
10         10 Patient_10    64 Hypertension            174      NA 2024-03-04    
11         11 Patient_11    41 Healthy                 180      NA 2024-03-11    
12         12 Patient_12    24 Diabetes                172     122 2024-03-18    
13         13 Patient_13    53 Diabetes                116      93 2024-03-25    
14         14 Patient_14    42 Hypertension            161     192 2024-04-01    
15         15 Patient_15    54 Healthy                 112     161 2024-04-08    
16         16 Patient_16    63 NA                      162     102 2024-04-15    
17         17 Patient_17    37 NA                      148     162 2024-04-22    
18         18 Patient_18    43 NA                      160     105 2024-04-29    
19         19 Patient_19    67 NA                      100     114 2024-05-06    
20         20 Patient_20    64 NA                      116     165 2024-05-13    
21         21 Patient_21    20 NA                      132     171 2024-05-20    
22         22 Patient_22    58 Hypertension            127     177 2024-05-27    
23         23 Patient_23    42 Healthy                 101      74 2024-06-03    
24         24 Patient_24    44 Diabetes                130     191 2024-06-10    
25         25 Patient_25    53 Hypertension            107      99 2024-06-17    
26         26 Patient_26    54 Diabetes                179     123 2024-06-24    
27         27 Patient_27    48 Healthy                 102      71 2024-07-01    
28         28 Patient_28    62 Hypertension            112      97 2024-07-08    
29         29 Patient_29    22 Diabetes                111     200 2024-07-15    
30         30 Patient_30    37 Diabetes                164     180 2024-07-22    
# ℹ 70 more rows
# ℹ Use `print(n = ...)` to see more rows

Strategies for handling missing data

Drop rows with missing values

df_dropped <- na.omit(df_with_missing)
dim(df_dropeed)                            # 88 7

Since we have 12 missing values in the df_with_missing data frame, removing them results to a reduced 88-row data frame.

Fill missing values with median or a default

median_glucose <- median(df_with_missing$Glucose, na.rm = TRUE)

df_filled <- df_with_missing |>
  mutate(
    Glucose   = ifelse(is.na(Glucose),   median_glucose, Glucose),
    Diagnosis = ifelse(is.na(Diagnosis), "Unknown",      Diagnosis)
  )

Forward fill using tidyr

library(tidyr)
df_ffill <- df_with_missing |> fill(Glucose, Diagnosis, .direction = "down")

cat(sprintf("Original shape:     %d rows\n", nrow(df_with_missing)))  # Original shape:     100 rows
cat(sprintf("After dropping NAs: %d rows\n", nrow(df_dropped)))       # After dropping NAs: 88 rows
cat(sprintf("After filling NAs:  %d rows\n", nrow(df_filled)))        # After filling NAs:  100 rows