4  Data Import

Part 2: Data Import & Cleaning

In this part, you will bring data into R and prepare it for analysis. We begin with one CSV file and add new tidyverse tools gradually.

4.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();
  • compare package data, URL imports, and local-file imports;
  • import a CSV file with a relative path;
  • explain the main parts of glimpse() output; and
  • identify common issues to investigate after an import.

4.2 Why import data?

So far, we have typed small examples directly into R. In reporting, the data usually come from somewhere else: a government website, an institution, a researcher, or a file supplied by a source. Importing brings that file into R so we can work with 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 a good format for learning data analysis.

Continue in the same project

Open djr.Rproj and create 02-data-import.Rmd. Download gpfg.csv and save it inside your project’s data folder. Do not open and resave the file in spreadsheet software.

4.3 Tidyverse, readr, and read_csv()

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

Loading the tidyverse also loads readr:

library(tidyverse)

Because readr is one of the core tidyverse packages, you do not also need to run library(readr). The single library(tidyverse) line makes read_csv() and the other core tidyverse tools available. If a project used readr without the rest of the tidyverse, it could load only readr with library(readr), but that is not the workflow used in this book.

The simplest form of the import function is:

read_csv("file_path.csv")

The function needs the path to the file. Because a path is text, it goes inside quotation marks.

Use read_csv(), not read.csv()

Both functions can import a CSV, but they come from different parts of R:

Function Where it comes from What it returns
read_csv() readr and the tidyverse A tibble
read.csv() Base R A traditional data frame

This book uses read_csv() for four reasons:

  1. It returns a tibble, the table format used throughout the tidyverse.
  2. It reports the column types it detected, giving us immediate information about the import.
  3. It records values that it could not convert correctly, which we can inspect with problems().
  4. Its name and behavior fit consistently with other tidyverse functions used in the book.

read.csv() is valid R code, but it belongs to a different workflow and its name uses a period instead of an underscore. Using one approach consistently makes the course easier to follow. Check the function name carefully: read_csv() is the one used here.

4.4 Package data, URLs, and local files

Data can enter R in several ways. Three common possibilities are package data, a direct URL, and a file stored on your computer.

Data supplied by a package

Some R packages include small datasets for examples and practice. For example, the ggplot2 package includes a table called mpg. It becomes available when the tidyverse is loaded:

library(tidyverse)
mpg

Package datasets are useful for learning because everyone with the same package receives the same data. In journalism, however, the main source is more often a file published by an organization or obtained through reporting.

Data imported from a URL

read_csv() can read a public CSV directly from its web address:

gpfg_online <- read_csv(
  "https://raw.githubusercontent.com/binchen19/djr/main/data/gpfg.csv"
)

This can be convenient for a quick exploration or an automated update. It also means that R must download the file again whenever the code runs.

Data imported from a local file

For the main course workflow, download the source file once, keep it unchanged inside the Project, and import the local copy. This is safer for a reproducible analysis because:

  • a website or URL may move, disappear, or become temporarily unavailable;
  • the contents at the same URL may be replaced without warning;
  • an internet connection may fail when the document is knitted;
  • a website may limit or block repeated downloads; and
  • the local file preserves the exact version used for the story.

Record the original URL and the date of download in your notes. A local copy is not a replacement for documenting the source; it is an archived snapshot of what the source provided at that time.

The workflow used in this book

The book mentions package data and URL imports so you can recognize them. The guided lessons use local CSV files stored in data/, which makes the code more reliable and allows the same analysis to run again later.

4.5 Relative paths and RStudio Projects

Our file is stored here inside the learner project:

djr/
├── data/
│   └── gpfg.csv
├── 02-data-import.Rmd
└── djr.Rproj

Starting from the project folder, the relative path is:

data/gpfg.csv

This is why we open the .Rproj file before working. Everyone who opens the same project starts in the same place, so the same relative path works on different computers.

Avoid personal paths

Do not use a path such as /Users/name/Desktop/... or C:/Users/name/Desktop/... in shared code. It points to one person’s computer. Keep the data inside the project and use a relative path.

4.6 Import the CSV

The teaching file comes from the equity-holdings data published by Norges Bank Investment Management. It is a prepared CSV containing one year of reported holdings.

Import it and save the result as gpfg:

gpfg <- read_csv("data/gpfg.csv")

Read the line from right to left: read_csv() reads the file, and <- saves the imported table with the name gpfg.

readr may display a message describing the rows, columns, and column types it found. This is information about the import, not an error.

4.7 Inspect the imported data

Never begin an analysis without looking at what was imported.

Look at the structure

glimpse() shows the number of rows and columns, the column names, their data types, and a few example values:

glimpse(gpfg)
Rows: 7,201
Columns: 13
$ year                  <dbl> 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2025, …
$ report_date           <date> 2025-12-31, 2025-12-31, 2025-12-31, 2025-12-31,…
$ region                <chr> "Oceania", "Oceania", "Oceania", "Oceania", "Oce…
$ country               <chr> "Australia", "Australia", "Australia", "Australi…
$ company               <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…
$ source_file           <chr> "gpfg-equities-2025-12-31.xlsx", "gpfg-equities-…
$ source_url            <chr> "https://www.nbim.no/api/investments/v2/report/?…

Read the output from top to bottom:

  • Rows: 7,201 tells us how many observations were imported.
  • Columns: 13 tells us how many variables were imported.
  • Each line beginning with $ describes one column.
  • The column name appears after $, such as company or market_value_usd.
  • The short label in angle brackets is the data type. For example, <chr> means text, <dbl> means a number, and <date> means a date.
  • The values at the end of the line are examples from that column.

For example, a line beginning $ company <chr> tells us that company is a text column. A line beginning $ market_value_usd <dbl> tells us that market_value_usd is numeric.

glimpse() is a quick overview. It shows only a few example values, so it cannot prove that every value is valid or tell us how many values are missing.

Preview a few rows

slice_head() shows rows from the beginning of a table. The n argument says how many rows to show:

gpfg |>
  slice_head(n = 5)

The result contains the same columns as gpfg, but only its first five rows. The original gpfg object is not changed.

Why use slice_head() instead of head()?

Both functions can show the first rows of a table, and head(gpfg) is valid R code. This book uses slice_head() because it belongs to dplyr, works naturally in a tidyverse pipeline, and makes the action explicit. In slice_head(n = 5), the named argument n clearly says how many rows to show.

What is a parsing problem?

A CSV stores values as text. During import, read_csv() tries to convert that text into suitable R values, such as numbers or dates. This conversion is called parsing.

Imagine that a column should contain numbers:

amount
120
unknown
95

The value unknown cannot be converted into a number. This is a parsing problem. Depending on the column type, the value may become NA, and readr records where the problem occurred.

After importing a file, use problems() to check whether readr recorded any such values:

problems(gpfg)

If problems are found, the result identifies the row and column, what readr expected, and the value it actually found. An empty result means readr did not detect a parsing problem. It does not prove that every value in the source is correct—for example, an incorrect but valid number would not be reported.

4.8 What should you inspect?

Importing a file successfully does not guarantee that the data are ready for analysis. Ask these questions before calculating anything:

Check Why it matters
Number of rows and columns An incomplete download or wrong file may have an unexpected size
Column names Names should identify the variables clearly
Data types A numeric column imported as <chr> may contain symbols or unexpected text
Missing values NA means a value is unavailable; it is not the same as zero
Unexpected values Negative amounts, impossible percentages, or unusual spellings may need investigation
Repeated rows A duplicate may be an error, or it may represent a legitimate repeated observation
Units and definitions A number cannot be interpreted correctly without knowing what it measures
Parsing problems A parsing problem means readr could not interpret a value as expected

At this stage, use glimpse(), slice_head(), problems(), and the data dictionary to form questions about the table. In the next chapter, we will use tidyverse functions to investigate missing, unexpected, and repeated values more systematically.

4.9 Refer to the data dictionary

Before using a column, check what it means and what unit it uses. Open the GPFG data dictionary and keep it as a reference. You do not need to import the dictionary into R for this lesson.

For example, the dictionary explains the difference between market value in Norwegian kroner and US dollars, and it explains that ownership values are percentage points.

One row in the imported file represents one reported equity holding at the end of the year. It does not represent a purchase or sale.

4.10 Practice

In an R Markdown document:

  1. load the tidyverse;
  2. import data/gpfg.csv as gpfg;
  3. inspect it with glimpse();
  4. show its first five rows; and
  5. check for parsing problems.

Then write five short answers:

  1. How many rows and columns were imported?
  2. What data types do company and market_value_usd have?
  3. What does one row represent?
  4. Name one important issue that glimpse() alone cannot fully check.
  5. Give one reason for importing the saved local file instead of downloading it from the URL every time the document runs.

4.11 Takeaways

Function or idea What it does
library(tidyverse) Loads the core tidyverse packages
read_csv("path") Imports a CSV file as a tibble
Relative path Locates a file from the RStudio Project folder
Local source copy Preserves the version used in the analysis
glimpse() Shows columns, types, and example values
slice_head(n = 5) Shows the first five rows
problems() Reports values readr could not parse as expected
Want more control?

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