3  Data Import

Part 2: Data Import & Cleaning

The previous chapters used values that we typed ourselves. We will now bring a real table into R, inspect what arrived, and prepare it for analysis. Chapters 3 and 4 follow the same file from import to a clean dataset.

3.1 Learning objectives

By the end of this chapter, you should be able to:

  • explain what a CSV file is;
  • explain the relationship between the tidyverse, readr, and read_csv();
  • import a local CSV with a relative path;
  • read the main parts of glimpse() output; and
  • identify clues that a newly imported table needs cleaning.

3.2 From a tibble to an external file

In Chapter 2, we used vectors and tibble() to create a small table ourselves. Journalists usually receive a much larger table from a government agency, company, researcher, or other source. Importing brings that external file into R as an object so we can inspect and analyse it.

We will use a CSV, or comma-separated values file. A CSV stores a table as plain text. It is widely used, easy to share, and can be opened by many programs without depending on one spreadsheet application.

Continue in the same project

Open djr.Rproj and create 03-data-import.Rmd. Download gpfg_messy.csv and save it inside the project’s data folder. Do not open and resave it in spreadsheet software before importing it.

The file is adapted from the 2025 equity-holdings data published by Norges Bank Investment Management. It contains the published values, with a few deliberately introduced problems for teaching: awkward names, a date stored as text, one incomplete row, and one duplicate. The amounts and percentages remain ordinary numeric values.

3.3 Tidyverse, readr, and read_csv()

The tidyverse is a collection of packages designed to work together. One of those packages is readr, which imports rectangular data files. The readr function for CSV files is read_csv().

Load the tidyverse at the beginning of the R Markdown document:

library(tidyverse)

Loading the tidyverse also loads readr, so we do not need a second library(readr) line. The simplest use of the function is:

read_csv("file_path.csv")

The function needs the path to the file. A path is text, so it appears inside quotation marks.

Why this book uses read_csv()

Base R also has a valid function named read.csv(). This book uses readr’s read_csv() because it returns the tibble used throughout the tidyverse and reports the column types it detected. Learning one consistent import workflow is enough for now.

3.4 Use a relative path

The learner project should now look like this:

djr/
├── data/
│   └── gpfg_messy.csv
├── 01-setup.Rmd
├── 02-r-basics.Rmd
├── 03-data-import.Rmd
└── djr.Rproj

Starting from the project folder, the path to the file is:

data/gpfg_messy.csv

This is a relative path: it describes where the file is located inside the project. Everyone who opens djr.Rproj starts from the same project folder, so the same path can work on different computers.

Avoid personal paths

A path such as /Users/name/Desktop/... or C:/Users/name/Desktop/... points to one person’s computer. Keep course files inside the project and use short relative paths.

3.5 Import the CSV

Import the table and assign it to an object named gpfg_raw:

gpfg_raw <- read_csv("data/gpfg_messy.csv")

Read the line from right to left: read_csv() reads the file, and <- saves the resulting tibble as gpfg_raw. The suffix _raw reminds us that this object still reflects the imported file; we have not cleaned it.

readr may print a message describing the rows, columns, and column types. That message is information about the import, not an error.

3.6 Inspect what arrived

Importing without an error does not mean the table is ready for analysis. Begin by looking at its structure and a few rows.

Look at the structure

glimpse() shows the number of rows and columns, each column name and type, and a few example values:

glimpse(gpfg_raw)
Rows: 7,203
Columns: 11
$ Year                  <dbl> 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2025, …
$ `Report Date`         <chr> "31/12/2025", "31/12/2025", "31/12/2025", "31/12…
$ region                <chr> "Oceania", "Oceania", "Oceania", "Oceania", "Oce…
$ country               <chr> "Australia", "Australia", "Australia", "Australi…
$ `Company Name`        <chr> "3P Learning Ltd", "ALS Ltd", "AMP Ltd", "ANZ Gr…
$ industry              <chr> "Consumer Discretionary", "Industrials", "Financ…
$ `Market Value NOK`    <dbl> 34140465, 1044536415, 393367522, 16360856591, 13…
$ market_value_usd      <dbl> 3384651, 103554273, 38998054, 1621998602, 131849…
$ voting_pct            <dbl> 3.00, 1.39, 1.27, 2.22, 1.68, 1.28, 1.17, 3.23, …
$ ownership_pct         <dbl> 3.00, 1.39, 1.27, 2.22, 1.68, 1.28, 1.17, 3.23, …
$ incorporation_country <chr> "Australia", "Australia", "Australia", "Australi…

Read the output from top to bottom:

  • Rows: 7,203 is the number of imported rows;
  • Columns: 11 is the number of variables;
  • each line beginning with $ describes one column;
  • <chr> means character text and <dbl> means a number; and
  • the values at the end of each line are examples, not the entire column.

Some observations should make us pause:

  • names such as Company Name and Market Value NOK contain spaces and punctuation, making them awkward to type in code;
  • Report Date is character text rather than an R date;
  • the market-value and percentage columns correctly appear as numbers; and
  • missing and duplicate rows will require checks that a quick preview cannot settle by itself.

These are not import failures. They are clues about work needed in Chapter 4.

Preview a few rows

Use the pipe from Chapter 2 to pass the imported table to slice_head():

gpfg_raw |>
  slice_head(n = 5)

slice_head(n = 5) returns the first five rows without changing gpfg_raw. The named argument n = 5 tells the function how many rows to show.

Why not use head()?

head(gpfg_raw) is valid base R. This book uses slice_head() because it is a dplyr function and fits the tidyverse pipelines used in later chapters.

3.7 Make an inspection checklist

After importing any dataset, ask:

Check Question to ask
Rows and columns Does the size match what the source describes?
Column names Are the names clear and convenient to use?
Column types Are amounts numeric and dates stored as dates?
Missing values Does NA mean unavailable, and how many are present?
Category values Are spelling, capitalization, and spaces consistent?
Repeated records Is each apparent duplicate an error or a valid observation?
Units and definitions What exactly does every number measure?

Chapter 4 will turn a small part of this checklist into code. We will rename a few columns, convert the date, inspect a missing row, identify a repeated row, and save the clean result for later chapters.

3.8 Keep the local source copy

read_csv() can also read a public URL, and some packages provide example datasets. Those approaches are useful to recognize, but the guided lessons use a saved local file. A local copy:

  • preserves the exact version used in the reporting;
  • continues to work if a URL changes or the internet is unavailable; and
  • avoids downloading the same file every time the document knits.

Record the publisher, original URL, and access date in your notes. Keeping a local file does not replace source documentation.

3.9 Refer to the data dictionary

Open the GPFG data dictionary and keep it beside the lesson. It explains what each column means and which unit it uses. You do not need to import the dictionary.

One row should represent one reported equity holding at the end of the year. It is not a purchase, sale, investment return, or payment received by a company. We will check that intended row meaning during cleaning.

3.10 Practice

In 03-data-import.Rmd:

  1. load the tidyverse;
  2. import data/gpfg_messy.csv as gpfg_raw;
  3. inspect the table with glimpse();
  4. show its first five rows; and
  5. compare the imported types with the types you think the columns need.

Then answer:

  1. How many rows and columns were imported?
  2. Which columns did R correctly import as numbers?
  3. Which column still needs to become a date?
  4. Name one awkward column name.
  5. Why does a successful import not prove that the table is clean?
  6. What should one row represent?

3.11 Takeaways

Function or idea What it does
library(tidyverse) Loads the core tidyverse packages
read_csv("path") Imports a CSV as a tibble
Relative path Locates a file from the RStudio Project folder
<- Saves the imported tibble as an object
glimpse() Shows column names, types, and example values
slice_head(n = 5) Shows the first five rows
Local source copy Preserves the exact file used in the reporting
Want more control?

The file path is the only read_csv() argument needed in this lesson. The official read_csv() reference explains optional controls for missing values, column types, delimiters, and unusual files when a project requires them.