Project 2 — Data Tidying: Electricity Production by Country

Author

Aniss Sahraoui

Published

October 10, 2026

library(tidyverse)
library(knitr)

# Shared styling, used across all three Project 2 reports
accent   <- "#2a78d6"
accent_2 <- "#eb6834"
theme_p2 <- theme_minimal(base_size = 12) +
  theme(panel.grid.minor = element_blank(), plot.title.position = "plot")

Data Source

This dataset comes from the Discussion 5A post “Total Energy Production by Country” by Ellie Wilser. The post uses Wikipedia’s list of countries by electricity production and explains the problem with it:

“The data is arranged with each country as a row and columns representing the different types of electricity generation methods. This data is untidy because of the multi-level headers, such as coal and gas being grouped together under fossil fuels.”

The post then asks two questions:

“1. What country is the most sustainable? (Highest amount of renewables? Lowest amount of fossils? Highest percentage of renewables?) 2. Can the countries be grouped or clustered based on their primary energy type?”

Source: Wikipedia, List of countries by electricity production (https://en.wikipedia.org/wiki/List_of_countries_by_electricity_production), which in turn draws on Ember’s electricity data.

How it was obtained: I scraped the first table on that page and wrote it to data/electricity_production_wide.csv, preserving the wide layout. The page uses a two-row header, which a CSV cannot hold directly, so I joined the two levels with a pipe: the column under Fossil fuels → Coal became Fossil fuels | Coal. No information is lost, the file stays untidy in exactly the way the post describes, and the tidying step has to split those names apart.

Data Structure Before Tidying

raw_power <- read_csv(
  "https://raw.githubusercontent.com/AnissSahraoui/DATA607/main/Week6/data/electricity_production_wide.csv",
  show_col_types = FALSE
)

dim(raw_power)
[1] 216  12
Code
names(raw_power)
 [1] "Location"               "Total (TWh)"            "Fossil fuels | Coal"   
 [4] "Fossil fuels | Gas"     "Fossil fuels | Other"   "Nuclear | Nuclear"     
 [7] "Renewables | Hydro"     "Renewables | Wind"      "Renewables | Solar"    
[10] "Renewables | Bioenergy" "Renewables | Other"     "Year"                  
Code
raw_power |> slice_head(n = 5) |> select(1:7)
# A tibble: 5 × 7
  Location      `Total (TWh)` `Fossil fuels | Coal` `Fossil fuels | Gas`
  <chr>                 <dbl>                 <dbl>                <dbl>
1 World                31747.                10491.               6904. 
2 China                10580.                 5773.                318. 
3 United States         4520.                  737.               1807. 
4 India                 2082.                 1474.                 48.5
5 Russia                1193.                  220.                533. 
# ℹ 3 more variables: `Fossil fuels | Other` <dbl>, `Nuclear | Nuclear` <dbl>,
#   `Renewables | Hydro` <dbl>

The file has 216 rows and 12 columns. It is untidy in three distinct ways:

  1. Two variables are buried in the column names. Fossil fuels | Coal encodes both an energy group and a specific source.
  2. Generation source is spread across columns rather than being a value in one column.
  3. Numbers are stored as text. Values like 10,579.70 carry thousands separators, so the columns import as character.

There is also a row for World, which is the sum of the country rows rather than a country.

Code
tibble(
  column = names(raw_power),
  type   = map_chr(raw_power, ~ class(.x)[1])
) |>
  slice_head(n = 6) |>
  kable()
column type
Location character
Total (TWh) numeric
Fossil fuels | Coal numeric
Fossil fuels | Gas numeric
Fossil fuels | Other numeric
Nuclear | Nuclear numeric

Transformation Steps

power <- raw_power |>
  # 1. Reshape wide to long. names_sep splits each "Group | Source" heading into
  #    two columns, which recovers the page's two-level header as real variables.
  pivot_longer(
    cols      = contains("|"),
    names_to  = c("energy_group", "source"),
    names_sep = " \\| ",
    values_to = "generation_twh"
  ) |>
  # 2. Rename the remaining columns to snake_case for consistency.
  rename(
    country       = Location,
    country_total = `Total (TWh)`,
    year          = Year
  ) |>
  # 3. Convert text to numbers by stripping the thousands separators.
  mutate(
    across(c(generation_twh, country_total),
           ~ as.numeric(str_remove_all(as.character(.x), ","))),
    year = as.integer(year),
    # 4. Normalize an ambiguous label: "Other" appears under BOTH groups, meaning
    #    oil-fired plants in one case and geothermal etc. in the other. Left alone,
    #    grouping by source would silently merge two different things.
    source = case_when(
      energy_group == "Fossil fuels" & source == "Other" ~ "Other fossil",
      energy_group == "Renewables"   & source == "Other" ~ "Other renewable",
      TRUE                                               ~ source
    )
  )

glimpse(power)
Rows: 1,944
Columns: 6
$ country        <chr> "World", "World", "World", "World", "World", "World", "…
$ country_total  <dbl> 31746.92, 31746.92, 31746.92, 31746.92, 31746.92, 31746…
$ year           <int> 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2…
$ energy_group   <chr> "Fossil fuels", "Fossil fuels", "Fossil fuels", "Nuclea…
$ source         <chr> "Coal", "Gas", "Other fossil", "Nuclear", "Hydro", "Win…
$ generation_twh <dbl> 10491.47, 6903.64, 823.92, 2813.09, 4433.08, 2711.33, 2…

Missing values. Blank cells are common in this table:

power |>
  summarise(
    rows            = n(),
    missing_values  = sum(is.na(generation_twh)),
    pct_missing     = round(100 * mean(is.na(generation_twh)), 1)
  )
# A tibble: 1 × 3
   rows missing_values pct_missing
  <int>          <int>       <dbl>
1  1944            914          47

On Wikipedia a blank cell means the country generates no measurable electricity from that source, not that the value is unknown: a country with no nuclear plants simply has an empty nuclear cell. The country totals confirm this reading, since they equal the sum of the non-blank entries. I therefore replace blanks with 0 rather than dropping them, because dropping the rows would make countries look as though they had fewer sources rather than zero output from some.

power <- power |> mutate(generation_twh = replace_na(generation_twh, 0))

# Verify the decision: do the per-source values now add up to the published total?
power |>
  group_by(country, country_total) |>
  summarise(sum_of_sources = sum(generation_twh), .groups = "drop") |>
  mutate(difference = abs(sum_of_sources - country_total)) |>
  summarise(
    countries       = n(),
    max_difference  = round(max(difference, na.rm = TRUE), 2),
    within_1_twh    = sum(difference < 1, na.rm = TRUE)
  ) |>
  kable()
countries max_difference within_1_twh
216 0.28 216

The sources add to the published total for every row, which confirms that blank means zero.

Separating countries from the world total. The World row is an aggregate, so including it in a country ranking would be double counting. Two countries report zero generation entirely, so they carry no mix to analyse and are excluded from percentage questions.

world <- power |> filter(country == "World")

countries <- power |>
  filter(country != "World") |>
  group_by(country) |>
  mutate(total_generation = sum(generation_twh)) |>
  ungroup() |>
  filter(total_generation > 0)

tibble(
  raw_rows            = nrow(raw_power),
  world_rows_removed  = 1,
  zero_output_removed = n_distinct(power$country) - 1 - n_distinct(countries$country),
  countries_analysed  = n_distinct(countries$country)
) |>
  kable()
raw_rows world_rows_removed zero_output_removed countries_analysed
216 1 2 213

Analytical Methods

The post asks about sustainability and about grouping countries by primary energy type. I answer them as follows:

  • Sustainability has three readings in the post, and they give different winners, so I report all three: the largest absolute renewable output, the highest share of renewables, and the share among countries large enough for the comparison to be meaningful (over 100 TWh).
  • Grouping uses each country’s dominant source: the single source producing the most electricity there. This is a deterministic classification rather than a clustering algorithm, so it needs no random seed and the group labels mean something concrete.

The world mix, for reference

Code
world |>
  mutate(share = 100 * generation_twh / country_total) |>
  arrange(desc(share)) |>
  transmute(`Energy group` = energy_group, Source = source,
            `TWh` = round(generation_twh),
            `Share of world (%)` = round(share, 1)) |>
  kable()
Energy group Source TWh Share of world (%)
Fossil fuels Coal 10491 33.0
Fossil fuels Gas 6904 21.7
Renewables Hydro 4433 14.0
Nuclear Nuclear 2813 8.9
Renewables Solar 2778 8.8
Renewables Wind 2711 8.5
Fossil fuels Other fossil 824 2.6
Renewables Bioenergy 703 2.2
Renewables Other renewable 89 0.3

Sustainability, three ways

Code
mix <- countries |>
  group_by(country, total_generation) |>
  summarise(
    renewable_twh = sum(generation_twh[energy_group == "Renewables"]),
    fossil_twh    = sum(generation_twh[energy_group == "Fossil fuels"]),
    nuclear_twh   = sum(generation_twh[energy_group == "Nuclear"]),
    .groups = "drop"
  ) |>
  mutate(
    pct_renewable = 100 * renewable_twh / total_generation,
    pct_fossil    = 100 * fossil_twh / total_generation
  )

mix |>
  slice_max(renewable_twh, n = 5) |>
  transmute(Country = country,
            `Renewable TWh` = round(renewable_twh),
            `Total TWh` = round(total_generation),
            `Renewable share (%)` = round(pct_renewable, 1)) |>
  kable(caption = "Largest renewable output in absolute terms")
Largest renewable output in absolute terms
Country Renewable TWh Total TWh Renewable share (%)
China 3913 10580 37.0
United States 1159 4520 25.6
Brazil 650 751 86.6
India 501 2082 24.1
Canada 417 652 63.9
Code
mix |>
  filter(pct_renewable >= 99.5) |>
  arrange(desc(total_generation)) |>
  transmute(Country = country,
            `Total TWh` = round(total_generation, 1),
            `Renewable share (%)` = round(pct_renewable, 1)) |>
  kable(caption = "Countries generating essentially all electricity from renewables")
Countries generating essentially all electricity from renewables
Country Total TWh Renewable share (%)
Paraguay 45.4 100
Ethiopia 33.4 100
Iceland 19.0 100
DR Congo 15.9 100
Costa Rica 12.8 100
Nepal 11.1 100
Bhutan 11.0 100
Albania 8.3 100
Central African Republic 0.1 100
Code
large <- mix |> filter(total_generation > 100)

large |>
  slice_max(pct_renewable, n = 12) |>
  mutate(country = fct_reorder(country, pct_renewable)) |>
  ggplot(aes(x = pct_renewable, y = country)) +
  geom_col(fill = accent, width = 0.65) +
  geom_text(aes(label = sprintf("%.0f%%", pct_renewable)), hjust = 1.2,
            color = "white", fontface = "bold", size = 3.3) +
  labs(title = "Norway and Brazil lead the large producers on renewable share",
       subtitle = "Countries generating more than 100 TWh per year",
       x = "Renewable share of generation (%)", y = NULL) +
  theme_p2 +
  theme(panel.grid.major.y = element_blank())
Figure 1: Renewable share of generation for countries producing more than 100 TWh. Only large producers are shown, because a country generating a few TWh can reach 100% with a single dam.
Code
large |>
  slice_max(pct_fossil, n = 5) |>
  transmute(Country = country,
            `Total TWh` = round(total_generation),
            `Fossil share (%)` = round(pct_fossil, 1)) |>
  kable(caption = "Most fossil-dependent large producers")
Most fossil-dependent large producers
Country Total TWh Fossil share (%)
Iraq 150 98.4
Bangladesh 103 97.9
Saudi Arabia 455 97.8
Iran 396 94.3
Egypt 246 87.0

Grouping countries by dominant source

Code
dominant <- countries |>
  group_by(country) |>
  slice_max(generation_twh, n = 1, with_ties = FALSE) |>
  ungroup() |>
  select(country, energy_group, source, total_generation)

dominant |>
  count(source, name = "countries") |>
  arrange(desc(countries)) |>
  kable(caption = "Number of countries by their single largest generation source")
Number of countries by their single largest generation source
source countries
Other fossil 64
Hydro 56
Gas 53
Coal 22
Nuclear 9
Wind 5
Solar 2
Bioenergy 1
Other renewable 1
Code
dominant |>
  count(energy_group, source, name = "countries") |>
  mutate(source = fct_reorder(source, countries)) |>
  ggplot(aes(x = countries, y = source, fill = energy_group)) +
  geom_col(width = 0.65) +
  geom_text(aes(label = countries), hjust = -0.3, size = 3.3) +
  scale_fill_manual(values = c("Fossil fuels" = accent_2, "Renewables" = "#1baf7a",
                               "Nuclear" = accent), name = NULL) +
  scale_x_continuous(expand = expansion(mult = c(0, 0.12))) +
  labs(title = "Oil and hydro dominate the most countries, for opposite reasons",
       x = "Number of countries", y = NULL) +
  theme_p2 +
  theme(legend.position = "top", panel.grid.major.y = element_blank())
Figure 2: Countries grouped by their dominant generation source, coloured by energy group.
Code
dominant |>
  group_by(energy_group) |>
  summarise(countries = n(),
            combined_twh = round(sum(total_generation)),
            .groups = "drop") |>
  arrange(desc(countries)) |>
  kable(col.names = c("Dominant energy group", "Countries", "Their combined output (TWh)"))
Dominant energy group Countries Their combined output (TWh)
Fossil fuels 139 26791
Renewables 65 3887
Nuclear 9 1035

Conclusions

  • “Most sustainable” depends entirely on how it is measured, which is the real finding here. By absolute renewable output the answer is China, at 3913 TWh, but that is only 37% of its own generation, because it also burns more coal than any other country. By share, nine countries are effectively 100% renewable, but most are small: the Central African Republic reaches 100% on 0.1 TWh, which is roughly a rounding error in China’s total.
  • Among large producers, Norway is the clearest answer, at 99% renewable on 161 TWh, almost entirely hydro, followed by Brazil at 87% on 751 TWh. Brazil is arguably the more impressive case, since it sustains that share at nearly five times Norway’s scale.
  • The most fossil-dependent large producers are oil and gas exporters: Iraq (98.4%), Bangladesh (97.9%) and Saudi Arabia (97.8%) all generate almost nothing from renewables.
  • Countries do group cleanly by dominant source, into 9 groups. The two largest are telling opposites: 64 countries run mostly on other fossil fuels, which here means oil, and these are overwhelmingly small island and developing states that burn imported diesel; 56 run mostly on hydro, usually because geography handed them a river. Only 7 countries are led by wind or solar, which shows how recent those technologies still are at national scale.
  • Fossil fuels dominate in 139 of the 213 countries analysed, and the world mix is still 57.4% fossil-fired. The countries with the greenest mixes are mostly those with the luck of a large river, not those that have transitioned away from coal.

The tidying step is what made the comparison possible: the renewable share of each country requires grouping by energy_group, a variable that existed only as a header spanning three columns in the source table.