Introduction

This code through demonstrates how to clean up messy column names and get a quick statistical overview of a real dataset, using the janitor and skimr packages in R.


Content Overview

Specifically, we’ll explain and demonstrate how to use the janitor and skimr packages to clean and format messy data so that it can be used in R for various purposes, using a real dataset of 2026 NFL quarterback stats pulled from Pro Football Reference.


Why You Should Care

This topic is valuable because real data is almost never ready to use as-is in R. In many cases, there are symbols in the columns, blank rows, and the formatting is built for the website it’s being displayed on, not R. For example, when I pulled stats from Pro Football Reference, the data would not cleanly show in R. Knowing how to clean this kind of data is something that could be consistently used going forward, since most of the actual work in analysis involves preparing the data before you do anything with it.


Learning Objectives

Take a messy CSV file with inconsistent column names and formatting, and load it into R

Translate those column names into a format R can work with cleanly, using clean_names()

Get a clear, readable statistical summary of the dataset using skim()



Cleaning the Data

Here, we’ll show how to turn a messy, real-world dataset into something clean and ready to analyze in R.


Further Exposition

This is based on the data cleaning type of work weve been doing all semester with dplyr, i just found a faster more automatic version of it to use with certain messy datasets. instead of renaming every column or writing out separate functions to check for missing values and summarize, ‘janitor’ and ‘skimr’ do it all for you in a few lines. Columns that have spaces or symbols like ‘%’ or ‘/’, like the original data I started with, don’t work very well in R. janitor::clean_names() fixes that automatically, and skimr::skim() gives you a full summary of the cleaned data, missing values, averages, distributions, all at once.


Basic Example

A basic example shows how clean_names() takes messy, real-world column names and turns them into something R can work with cleanly.

# Load the raw data
qb_stats <- read.csv("qb_stats_2026.csv", skip = 1)
names(qb_stats)
##  [1] "Rk"                "Player"            "Age"              
##  [4] "Team"              "Pos"               "G"                
##  [7] "GS"                "QBrec"             "Cmp"              
## [10] "Att"               "Cmp."              "Yds"              
## [13] "TD"                "TD."               "Int"              
## [16] "Int."              "X1D"               "Succ."            
## [19] "Lng"               "Y.A"               "AY.A"             
## [22] "Y.C"               "Y.G"               "Rate"             
## [25] "QBR"               "Sk"                "Yds.1"            
## [28] "Sk."               "NY.A"              "ANY.A"            
## [31] "X4QC"              "GWD"               "Awards"           
## [34] "Player.additional"
# Clean the column names
qb_clean <- qb_stats %>%
  clean_names()
names(qb_clean)
##  [1] "rk"                "player"            "age"              
##  [4] "team"              "pos"               "g"                
##  [7] "gs"                "q_brec"            "cmp"              
## [10] "att"               "cmp_2"             "yds"              
## [13] "td"                "td_2"              "int"              
## [16] "int_2"             "x1d"               "succ"             
## [19] "lng"               "y_a"               "ay_a"             
## [22] "y_c"               "y_g"               "rate"             
## [25] "qbr"               "sk"                "yds_1"            
## [28] "sk_2"              "ny_a"              "any_a"            
## [31] "x4qc"              "gwd"               "awards"           
## [34] "player_additional"

Notice the difference: Cmp., TD., and X1D become clean, lowercase names like cmp_2, td_2, and x1d. The messy names happened because R converted PFR’s % and / symbols into periods when the file was first read in.


Advanced Examples

More specifically, this can be used for getting a full statistical overview of a dataset in a single function call.

skim(qb_clean)
Data summary
Name qb_clean
Number of rows 57
Number of columns 34
_______________________
Column type frequency:
character 5
logical 1
numeric 28
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
player 0 1 6 18 0 57 0
team 0 1 0 3 1 33 0
pos 0 1 0 2 1 4 0
q_brec 0 1 0 5 17 14 0
player_additional 0 1 5 8 0 57 0

Variable type: logical

skim_variable n_missing complete_rate mean count
awards 57 0 NaN :

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
rk 1 0.98 28.50 16.31 1.0 14.75 28.50 42.25 56.00 ▇▇▇▇▇
age 1 0.98 28.95 4.71 22.0 25.00 28.00 31.25 43.00 ▇▇▅▂▁
g 1 0.98 3.07 1.16 1.0 2.00 4.00 4.00 4.00 ▂▃▁▂▇
gs 1 0.98 2.46 1.64 0.0 1.00 3.00 4.00 4.00 ▃▂▃▁▇
cmp 1 0.98 48.39 38.49 0.0 8.00 44.00 81.25 121.00 ▇▃▂▅▃
att 1 0.98 75.29 59.24 1.0 13.50 70.00 121.25 180.00 ▇▅▂▅▅
cmp_2 0 1.00 59.12 24.93 0.0 57.50 63.60 69.80 100.00 ▂▁▃▇▁
yds 1 0.98 536.20 432.24 0.0 62.00 479.00 918.25 1268.00 ▇▃▂▃▅
td 1 0.98 3.59 3.34 0.0 0.00 3.00 6.00 11.00 ▇▃▃▂▂
td_2 0 1.00 3.58 2.88 0.0 0.00 3.70 5.60 9.70 ▇▆▆▃▂
int 1 0.98 1.62 1.78 0.0 0.00 1.00 3.00 7.00 ▇▂▃▁▁
int_2 0 1.00 3.55 13.20 0.0 0.00 1.70 2.60 100.00 ▇▁▁▁▁
x1d 1 0.98 25.55 20.85 0.0 3.50 21.50 43.25 63.00 ▇▅▃▅▃
succ 9 0.84 47.90 14.18 23.5 40.62 46.45 52.48 100.00 ▃▇▂▁▁
lng 10 0.82 44.70 18.74 8.0 32.50 47.00 56.00 82.00 ▅▅▇▆▂
y_a 0 1.00 6.16 3.29 0.0 5.60 6.90 7.50 16.00 ▂▅▇▁▁
ay_a 0 1.00 5.27 7.65 -45.0 4.90 6.40 8.40 16.00 ▁▁▁▂▇
y_c 7 0.88 10.56 2.85 0.0 9.93 10.85 12.07 16.00 ▁▁▂▇▁
y_g 0 1.00 156.30 101.58 0.0 63.00 181.50 239.50 317.00 ▇▂▆▇▆
rate 0 1.00 84.02 27.68 0.0 73.00 86.50 105.00 127.30 ▁▃▂▇▅
qbr 5 0.91 52.81 27.16 0.0 37.05 59.55 71.20 100.00 ▃▃▅▇▃
sk 1 0.98 4.98 4.45 0.0 0.00 5.00 8.25 13.00 ▇▂▃▃▃
yds_1 1 0.98 30.88 29.39 0.0 0.00 27.00 51.25 107.00 ▇▅▃▁▁
sk_2 0 1.00 4.82 3.86 0.0 0.00 4.80 7.83 13.33 ▇▅▆▃▂
ny_a 0 1.00 5.54 3.16 0.0 4.90 6.00 6.80 16.00 ▃▇▆▁▁
any_a 0 1.00 4.66 7.53 -45.0 4.20 6.00 7.40 16.00 ▁▁▁▂▇
x4qc 1 0.98 0.36 0.59 0.0 0.00 0.00 1.00 2.00 ▇▁▃▁▁
gwd 1 0.98 0.45 0.71 0.0 0.00 0.00 1.00 3.00 ▇▃▁▁▁


What’s more, it can also be used for quickly counting and summarizing categorical columns, like how many players at each position appear in this dataset.

qb_clean %>%
  tabyl(pos)


Most notably, it’s valuable for making the cleaned data usable in a real analysis, here, filtering out quarterbacks with very few pass attempts and ranking the rest by passer rating.

qb_clean %>%
  filter(att >= 50) %>%
  arrange(desc(rate)) %>%
  select(player, team, att, cmp_2, rate) %>%
  head(10)



Further Resources

Learn more about ‘janitor’ and ‘skimr’ with the following:




Works Cited

This code through references and cites the following sources: