Project 2 — Dataset 3: Livestock Production (FAOSTAT)

Author

NOELLE

Published

October 1, 2026

Introduction

My classmate Bowen Cho posted about using FAOSTAT’s livestock production data on the Week 5 Discussion 5A board — he’d worked with it years ago using base R loops and wanted to redo it with tidyverse. He didn’t attach a file, so I pulled real data from FAOSTAT’s own bulk download myself, since it’s the same public source he named.

Approach

FAOSTAT’s full livestock file covers every country and product since 1961, which is way more than I need, so I will first cut it down to a manageable slice — 8 countries, 4 livestock products, years 2000 through 2023 — and save that as the raw wide CSV, keeping the year-by-column layout FAOSTAT itself uses. Then I will bring it into R and reshape the year columns into a proper year variable with pivot_longer(), keeping missing production values as NA. Since Bowen’s whole point was comparing an old, slow way of doing this to a modern one, I will also time a base R loop against the tidyverse version, the way he described. Once it’s tidy, I will look at how production of each product changed from 2000 to 2023 and which country produces the most of any one product.

Data source

FAO FAOSTAT, “Crops and Livestock Products” domain, bulk download: https://www.fao.org/faostat/en/#data/QCL. I downloaded the full bulk file (Production_Crops_Livestock_E_All_Data.zip, updated December 2025) and filtered it down to the Production element (in tonnes) for four livestock products — cattle meat, chicken meat, cow milk, and hen eggs — across eight countries (Australia, Brazil, China, France, Germany, India, Kenya, and the United States), for the years 2000–2023.

Data structure before tidying

The raw file has 32 rows (8 countries × 4 products) and 26 columns: Country, Item, and then one column per year from 2000 to 2023, holding the production quantity in tonnes. It’s wide because each year is a separate column instead of year being its own variable.

Code
library(tidyverse)

url <- "https://raw.githubusercontent.com/NawelMe/DATA607-Fall-2026/main/week-06/project-2/livestock_production.csv"
raw <- read_csv(url, show_col_types = FALSE)

raw |> select(Country, Item, `2000`, `2010`, `2023`)
# A tibble: 32 × 5
   Country   Item           `2000`   `2010`   `2023`
   <chr>     <chr>           <dbl>    <dbl>    <dbl>
 1 Australia Cattle meat   1987902  2128598  2234836
 2 Australia Chicken meat   610000   934352  1413189
 3 Australia Cow milk     10847000  9023000  8467000
 4 Australia Hen eggs       143000   174000   240010
 5 Brazil    Cattle meat   6578800  6977484  8962423
 6 Brazil    Chicken meat  5980600 10692556 13293837
 7 Brazil    Cow milk     20379988 31636924 36305658
 8 Brazil    Hen eggs      1732489  2252054  3440030
 9 China     Cattle meat   4642024  5676946  6789201
10 China     Chicken meat  9064191 12184797 15483431
# ℹ 22 more rows

The table contains 32 rows and 26 columns, as expected. Some production values are missing, and I will preserve those missing values when reshaping the data.

Transformation steps

Code
tidy <- raw |>
  rename_with(tolower) |>
  pivot_longer(
    cols      = -c(country, item),
    names_to  = "year",
    values_to = "tonnes"
  ) |>
  mutate(year = as.integer(year))

tidy
# A tibble: 768 × 4
   country   item         year  tonnes
   <chr>     <chr>       <int>   <dbl>
 1 Australia Cattle meat  2000 1987902
 2 Australia Cattle meat  2001 2078900
 3 Australia Cattle meat  2002 2089742
 4 Australia Cattle meat  2003 1997563
 5 Australia Cattle meat  2004 2112892
 6 Australia Cattle meat  2005 2090272
 7 Australia Cattle meat  2006 2188318
 8 Australia Cattle meat  2007 2168946
 9 Australia Cattle meat  2008 2138328
10 Australia Cattle meat  2009 2106385
# ℹ 758 more rows
  • rename_with(tolower) lowercases Country and Item so every column name in the tidy table is lowercase, matching the rest of the project.
  • pivot_longer() is the reshape: the 24 year columns collapse into two columns, year (which year it is) and tonnes (the production number for that country, product, and year).
  • as.integer(year) turns the year, which comes out of the column names as text, back into a real number so it sorts and plots correctly.
Code
n_missing <- sum(is.na(tidy$tonnes))
pct_missing <- round(100 * n_missing / nrow(tidy), 1)
n_missing
[1] 30
Code
pct_missing
[1] 3.9

There are 30 missing values out of 768 observations (3.9%). These are all 24 years of India’s cattle meat series and six years of France’s hen egg series. The CSV does not explain why these values are missing. I will keep them as NA because missing data does not mean zero production. The totals below exclude missing values, so they represent the available observations.

A detour: comparing the old way to the new way

Bowen’s post mentioned he used to reshape FAOSTAT data with base R loops, so here’s that comparison — a loop-based reshape against pivot_longer(), both doing the exact same wide-to-long job, timed with system.time().

Code
base_r_reshape <- function(df) {
  year_cols <- setdiff(names(df), c("Country", "Item"))
  out <- data.frame()
  for (i in seq_len(nrow(df))) {
    for (yr in year_cols) {
      out <- rbind(out, data.frame(
        country = df$Country[i],
        item    = df$Item[i],
        year    = as.integer(yr),
        tonnes  = df[[yr]][i]
      ))
    }
  }
  out
}

time_base_r    <- system.time(base_r_reshape(raw))
time_tidyverse <- system.time(
  raw |> rename_with(tolower) |>
    pivot_longer(cols = -c(country, item), names_to = "year", values_to = "tonnes") |>
    mutate(year = as.integer(year))
)

tibble(
  Approach     = c("Base R loop (rbind row by row)", "tidyverse pivot_longer()"),
  `Time (sec)` = c(time_base_r[["elapsed"]], time_tidyverse[["elapsed"]])
) |> knitr::kable()
Approach Time (sec)
Base R loop (rbind row by row) 0.131
tidyverse pivot_longer() 0.003

The loop adds rows one at a time using rbind(), while pivot_longer() reshapes the year columns in one operation. The table shows the elapsed time for one run of each method on this dataset. To draw conclusions about performance on larger datasets, I would repeat the timing with larger samples.

Analytical methods

I will answer two questions using the tidy long table:

  1. 2000 vs. 2023 — the percentage change in total production for each livestock product, using the available data from the eight-country sample.
  2. Largest production quantities — the five largest country/product combinations in 2023.
Code
change <- tidy |>
  filter(year %in% c(2000, 2023)) |>
  pivot_wider(names_from = year, values_from = tonnes, names_prefix = "y") |>
  summarise(total_2000 = sum(y2000, na.rm = TRUE),
            total_2023 = sum(y2023, na.rm = TRUE),
            .by = item) |>
  mutate(pct_change = round(100 * (total_2023 - total_2000) / total_2000, 1)) |>
  arrange(desc(pct_change))

change |>
  rename(Product = item, `2000 (t)` = total_2000, `2023 (t)` = total_2023, `% change` = pct_change) |>
  knitr::kable()
Product 2000 (t) 2023 (t) % change
Cow milk 202475230 379681730 87.5
Chicken meat 32212347 57395739 78.2
Hen eggs 33089763 55180670 66.8
Cattle meat 28313028 32817025 15.9
Code
ggplot(change, aes(x = reorder(item, pct_change), y = pct_change)) +
  geom_col(fill = "#1F4E79") +
  coord_flip() +
  labs(title = "Change in production, 2000 to 2023 (8-country sample)",
       x = NULL, y = "% change") +
  theme_minimal()

Across the available observations in this eight-country sample, cow milk production increased by 87.5%, chicken meat by 78.2%, hen eggs by 66.8%, and cattle meat by 15.9%. Cow milk had the largest percentage increase, while cattle meat had the smallest. These comparisons describe the sample and do not establish why production changed. France’s hen egg value is missing in 2023, so the egg totals do not include the same countries in both years.

Code
biggest <- tidy |>
  filter(year == 2023) |>
  slice_max(tonnes, n = 5)

biggest |>
  rename(Country = country, Product = item, Year = year, Tonnes = tonnes) |>
  knitr::kable()
Country Product Year Tonnes
India Cow milk 2023 127105138
United States of America Cow milk 2023 102653065
China Cow milk 2023 42439009
Brazil Cow milk 2023 36305658
China Hen eggs 2023 36054385

India’s cow milk production is the largest figure in this sample, at 127,105,138 tonnes in 2023. The next largest is United States cow milk production, at 102,653,065 tonnes. India’s figure is about 24% higher.

Conclusions

Cow milk and chicken meat had the largest percentage increases in this sample, while cattle meat grew more slowly. The data show how production changed, but additional information would be needed to explain those changes.

The timing comparison shows how long each reshaping method took in one run on a small dataset. I would repeat the comparison with larger datasets before making a broader claim about speed.

I kept missing values as NA because an unavailable value is not the same as zero production. Missing observations also limit the comparisons: France’s hen egg production is included in the 2000 total but unavailable for 2023.

I would extend the analysis by including more countries and comparing production per person. For the growth comparison, I would also restrict each product to countries with data in both 2000 and 2023.

AI Use

Anthropic. (2026). Claude Sonnet 5 [Large language model]. https://claude.ai. Accessed September 30, 2026.

I used Claude to proofread my writing and help me find mistakes in my R code. I reviewed the suggestions and checked the changes before including them.