5  Analysis I

Part 3: Data Analysis

Chapter 4 produced a clean 2025 table. We can already use that single annual snapshot to describe values, rank holdings, and compare groups. Chapter 6 will add several years so that we can also investigate change over time.

5.1 Learning objectives

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

  • turn a broad topic into a precise reporting question;
  • inspect a clean table before analysing it;
  • distinguish description, ranking, comparison, relationship, and trend questions;
  • calculate summaries with summarise();
  • rank rows with arrange();
  • compare groups with group_by() and summarise(); and
  • turn a calculated result into a carefully worded finding.

5.2 Continue with the clean 2025 data

Continue in the same project

Open djr.Rproj and create 05-data-analysis-i.Rmd. Reuse the data/gpfg_2025.csv file created in Chapter 4. A ready-made gpfg_2025.csv is available if needed.

Start the new document by loading the tidyverse and importing the clean file:

library(tidyverse)

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

One row represents one equity holding reported by NBIM at the end of 2025. A row is not a purchase, sale, investment return, or payment received by a company.

Here, a holding means the fund’s reported ownership stake in a company at the reporting date. Saying that a company appears in the data does not mean that it received cash from Norway in 2025.

5.3 A question-to-answer workflow

Use the same sequence for every analysis:

  1. State what one row represents.
  2. Write a precise question naming the measure, group, and time period.
  3. Identify the columns needed to answer it.
  4. Check their types, missing values, units, and definitions.
  5. Calculate and inspect the result.
  6. Verify important or surprising values.
  7. Write only what the evidence supports.

The function should follow from the question, not the other way around.

5.4 Know the dataset before asking questions

Recheck the clean table:

glimpse(gpfg)
Rows: 7,201
Columns: 11
$ 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…

The output should show 7,201 rows, 11 columns, convenient names, numeric value columns, and a real date column.

distinct(column) keeps each observed value in a column once. arrange(column) then orders rows from smallest to largest or, for text, alphabetically. Use both to inspect the available region labels:

gpfg |>
  distinct(region) |>
  arrange(region)

The result is a short alphabetical list rather than thousands of repeated region labels.

count(region) returns one row per region and creates a count column named n. arrange(desc(n)) sorts that column in descending order, with the largest count first:

gpfg |>
  count(region) |>
  arrange(desc(n))

The result describes the number of rows. It does not yet tell us which region has the largest total market value. Count and amount answer different questions.

5.5 Types of questions in data journalism

Question type What it asks Example using the 2025 data
Description How much is there, or what is typical? Across NBIM’s 2025 company holdings, what were the total and median market values?
Ranking Which observations are largest or smallest? Which companies had the largest reported holding values in 2025?
Comparison How do categories differ? Which investment markets accounted for the largest 2025 totals?
Relationship Do two measures vary together? Were higher ownership percentages associated with larger holding values?
Trend How did a measure change over time? How did NBIM’s reported equity-holdings total change from 2021 to 2025?

The clean 2025 file can answer the first four types because it contains many holdings, categories, and numeric measures. It cannot answer a trend question: one year provides no earlier or later observation for comparison. Chapter 6 will solve that limitation.

5.6 Ask a descriptive question

Across the company holdings reported by NBIM at the end of 2025, what were the total and median market values?

summarise() reduces many rows to calculated summary values. Its basic form is summarise(new_name = calculation): the name on the left labels the result column, and the calculation on the right produces its value.

holding_summary <- gpfg |>
  summarise(
    total_market_value_nok = sum(market_value_nok),
    average_market_value_nok = mean(market_value_nok),
    median_market_value_nok = median(market_value_nok),
    smallest_market_value_nok = min(market_value_nok),
    largest_market_value_nok = max(market_value_nok)
  )

holding_summary

One row in holding_summary describes the full 2025 table. Financial values are often uneven, so compare the mean and median before calling either value “typical.” A few very large holdings can pull the mean upward.

5.7 Ask a ranking question

Which ten companies in NBIM’s 2025 equity-holdings data had the largest reported holding values, measured in NOK?

The question needs the company, market, industry, and NOK value. select(column1, column2) keeps the named columns. arrange(column) sorts from smallest to largest, while arrange(desc(column)) sorts from largest to smallest. After sorting, slice_head(n = 10) keeps the first ten rows:

largest_holdings <- gpfg |>
  select(company, country, industry, market_value_nok) |>
  arrange(desc(market_value_nok)) |>
  slice_head(n = 10)

largest_holdings

slice_head(n = 10) keeps the first ten rows after sorting. A ranking identifies large observations; it does not explain why they are large. Verify important rows against the source before publication.

5.8 Ask a comparison question

Which investment markets accounted for the largest total market value of NBIM’s reported equity holdings at the end of 2025?

This question needs one total per country. group_by(country) tells dplyr to treat each country as a separate group; it does not calculate a result by itself. The following summarise() returns one row for each group. Inside it, n() counts rows and sum() adds their values.

Watch grouping and summarising

Play the animation below and pause as each group becomes one summary row. Its cat1 column contains categories, like our country column; its x column contains numbers, like our market_value_nok column.

Animation by Andrew Heiss, used unchanged under CC BY 4.0. The animation uses summarize(), an alternative spelling of summarise(); both do exactly the same thing.

Watch three steps:

  1. group_by(cat1) identifies which rows belong together. The animation separates them visually, but R keeps one table and does not reorder its rows just because it is grouped.
  2. summarise() calculates the mean, total, and row count separately for each group.
  3. The result contains one row per category, with its calculated summaries.

Without grouping, our earlier summary described the whole dataset in one row. Now we want a separate count and total for each country. Before running the code, predict what one row of the result will represent:

country_summary <- gpfg |>
  group_by(country) |>
  summarise(
    holding_records = n(),
    market_value_nok = sum(market_value_nok)
  ) |>
  arrange(desc(market_value_nok))

country_summary |>
  slice_head(n = 10)

Track the changing row meaning:

  • before summarise(), one row represents one holding;
  • after summarise(), one row represents one investment market.

Record count and total value need not produce the same ranking.

Calculate a share

mutate() adds or changes columns while keeping the existing rows. Its basic form is mutate(new_column = calculation). Use it to add each market’s percentage share of the full reported value:

country_summary <- country_summary |>
  mutate(
    share_percent = market_value_nok / sum(market_value_nok) * 100
  )

country_summary |>
  slice_head(n = 10)

The denominator is the sum of the country totals. The resulting percentage answers: “What share of the total market value in NBIM’s 2025 equity-holdings data is associated with this investment market?”

5.9 Prepare a relationship question

Among companies in NBIM’s 2025 equity-holdings data, were higher ownership percentages associated with larger reported holding values?

The required columns already exist in the single-year file:

relationship_data <- gpfg |>
  select(company, market_value_usd, ownership_pct)

relationship_data |>
  slice_head(n = 5)

A relationship is difficult to judge from thousands of printed rows. Chapter 7 will turn these two numeric columns into a scatterplot. Preparing and checking the relevant table still comes before making the chart.

5.10 From a result to a finding

Compare these statements:

  • Too broad: “The United States received the most Norwegian investment.”
  • Better: “The United States had the largest reported market value in NBIM’s 2025 year-end equity-holdings file.”

The second statement matches the measure, time, and source. Explanation requires additional reporting beyond the calculated table.

5.11 What this chapter cannot answer

The 2025 table cannot tell us whether a value increased, decreased, or stayed stable. A trend requires the same measure at multiple points in time. Chapter 6 will import five annual snapshots, combine their rows, and add that missing time dimension.

5.12 Practice

  1. Use distinct() to inspect the industry labels.
  2. Use count() to find the most frequently occurring industries.
  3. Calculate the total, mean, and median USD value of the company holdings.
  4. Rank the ten largest company holdings by USD value.
  5. Compare total market value and record count across industries.
  6. Write one supported finding and one explanatory question requiring more evidence.
  7. Write one trend question that this single-year file cannot answer.

5.13 Takeaways

Function or idea What it does
distinct() Shows the unique values present in a column
count() Counts rows within categories
summarise() Reduces rows to calculated summary values
select() Keeps the columns required for a question
arrange() + desc() Orders rows from largest to smallest
slice_head() Keeps the first rows after ordering
group_by() + summarise() Produces one summary row per group
n() Counts rows inside each group
mutate() Adds a derived measure such as percentage share
Want more control?

The official dplyr reference contains additional summary and ranking tools. Add one only when a reporting question requires it.