Tutorial #2. Data visualization and summary statistics
Introduction to programming for data analysis
M1 Economics / Intro to Programming / tutorial-2
tutorial-2/
data/
out/ gdp-lifesat.csv
scripts/
Disclaimer
This HTML page was generated with Claude Sonnet 5 based on exercises we developed from a LaTeX document.
If you would prefer to work with a more traditional PDF version, you can download it here: Download the PDF.
Part of the content was created by Pierre Michel (AMSE) and Morgan Raux (AMSE) who kindly shared their work.
In the previous tutorial, you downloaded datasets from Eurostat, merged them into a single dataset in R, and exported the result as a CSV file.
Objectives of the tutorial
In this tutorial, you will work with the dataset from the second exercise of Tutorial #1 to:
- explore it through basic summary statistics,
- create your first data visualizations in R using
ggplot2.
Did not finish tutorial 1?
Didn’t get to finish combining the data in Tutorial 1? Grab the ready-to-use file below and save it to the data/out/ folder of your project.
💡 Clean session
If you need to have a clean session in RStudio, first remove all the objects in memory by evaluating the following instruction:
Then, on the menu “Session”, click on “Restart R”.
Setup
Prepare a project for the second tutorial. Adopt the tree structure presented below. Note that launching your session via project.Rproj sets your active working directory to tutorial-2/.
tutorial-2/
data/
raw/
tmp/
out/
gdp-lifesat.csv
scripts/
figs/
tables/
references/
README.md
project.Rproj
Q1. Import the dataset you exported at the end of Tutorial #1 (gdp-lifesat.csv, assumed to be copied into your data entry structure).
The file lives in your data/out/ folder — build the relative path from your project root (tutorial-2/).
library(readr)
# Assuming the file was moved to the data output folder of the project root
tb <- read_csv("data/out/gdp-lifesat.csv")Rows: 231 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (1): country
dbl (3): year, gdp, life_satisf
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# A tibble: 231 × 4
country year gdp life_satisf
<chr> <dbl> <dbl> <dbl>
1 AL 2018 4730 5.5
2 AL 2021 5190 5.7
3 AL 2022 5500 6
4 AT 2013 43080 7.8
5 AT 2018 45140 8
6 AT 2021 44580 8
7 AT 2022 46430 7.9
8 AT 2023 45670 7.7
9 AT 2024 45140 7.6
10 AT 2025 45390 7.7
# ℹ 221 more rows
Plot Distributions
Q2. Read the following code, evaluate it, and explain what it does.
This block initialises a graphic via ggplot2 using the tb dataset. It maps the continuous metric gdp to the x-axis and draws an empirical density distribution via geom_density(), filled with light blue (10% opacity). It adds clean labels and applies a minimal theme. The background grid lines are removed.

Q3. Using the ggsave() function, export this graph as a PDF file in the figs folder of your project environment.
Q4. Plot the distribution of the variable measuring life satisfaction using a histogram:
- Label the x-axis as “Overall life satisfaction”.
- Remove the y-axis title.
- Add the plot title “Histogram of Overall life satisfaction”.
Use geom_histogram(), and set y = NULL inside labs() to remove an axis title.
ggplot(data = tb, mapping = aes(x = life_satisf)) +
geom_histogram(bins = 15, fill = "darkgray", color = "white") +
labs(
x = "Overall life satisfaction",
y = NULL, # Removes the y-axis title
title = "Histogram of Overall life satisfaction"
) +
theme_minimal()Warning: Removed 3 rows containing non-finite outside the scale range
(`stat_bin()`).

Summary Statistics
Q5. Examine and evaluate the following instructions. Then, explain what they do.
This pipeline of instructions extracts the features gdp and life_satisf from the data frame tb and passes them directly into R’s base summary function summary(). This returns the descriptive quantities (Minimum, 1st Quartile, Median, Mean, 3rd Quartile, Maximum) alongside missing item flags (NAs) per variable.
gdp life_satisf
Min. : 4730 Min. :4.800
1st Qu.: 16000 1st Qu.:6.800
Median : 23310 Median :7.200
Mean : 32362 Mean :7.108
3rd Qu.: 44210 3rd Qu.:7.600
Max. :107570 Max. :8.100
NAs :3
Q6. Create a table of summary statistics for the variables gdp and life_satisf. The table should include the following statistics: mean, standard deviation, minimum, maximum. Round all values to two decimal places. The resulting table must have two rows (one for gdp, one for life_satisf). Name this table summary_stats.
To get one row per variable, first reshape the two columns into a long format with pivot_longer().
Then group_by(var) and summarise() with mean(), sd(), min(), max(), each wrapped in round(..., 2).
Using tidyverse strategies to explicitly construct long summaries:
library(tidyr)
summary_stats <- tb |>
select(gdp, life_satisf) |>
pivot_longer(
cols = everything(), names_to = "var", values_to = "val"
) |>
group_by(var) |>
summarise(
mean = round(mean(val, na.rm = TRUE), 2),
sd = round(sd(val, na.rm = TRUE), 2),
min = round(min(val, na.rm = TRUE), 2),
max = round(max(val, na.rm = TRUE), 2)
)
summary_stats# A tibble: 2 × 5
var mean sd min max
<chr> <dbl> <dbl> <dbl> <dbl>
1 gdp 32362. 23512. 4730 107570
2 life_satisf 7.11 0.67 4.8 8.1
Q7. Examine and evaluate the following instruction. Then, explain what it does.
It replaces the names of the columns in the summary_stats summary table with those provided in the vector (c()). Here the names happen to already match, so the table is unchanged — but this instruction is what you would use to rename columns coming out of a pipeline with less convenient default names.
# A tibble: 2 × 5
var mean sd min max
<chr> <dbl> <dbl> <dbl> <dbl>
1 gdp 32362. 23512. 4730 107570
2 life_satisf 7.11 0.67 4.8 8.1
Q8. Print the content of the object summary_stats as a LaTeX-formatted table. To do so, use the {stargazer} package. Adapt the following code:
Save the LaTeX table into a file named summary_stats.tex inside your output data subfolder.
Please cite as:
Hlavac, Marek (2022). stargazer: Well-Formatted Regression and Summary Statistics Tables.
R package version 5.2.3. https://CRAN.R-project.org/package=stargazer
% Table created by stargazer v.5.2.3 by Marek Hlavac, Social Policy Institute. E-mail: marek.hlavac at gmail.com
% Date and time: Wed, Sep 16, 2026 - 19:15:21
\begin{table}[!htbp] \centering
\caption{}
\label{}
\begin{tabular}{@{\extracolsep{5pt}} cccccc}
\\[-1.8ex]\hline
\hline \\[-1.8ex]
& var & mean & sd & min & max \\
\hline \\[-1.8ex]
1 & gdp & 32361.69 & 23511.96 & 4730 & 107570 \\
2 & life\_satisf & 7.11 & 0.67 & 4.8 & 8.1 \\
\hline \\[-1.8ex]
\end{tabular}
\end{table}
Rank countries by GDP in 2023
Q9. Create an object called tb_2023 that contains only the rows of the dataset where the year is 2023.
# A tibble: 33 × 4
country year gdp life_satisf
<chr> <dbl> <dbl> <dbl>
1 AT 2023 45670 7.7
2 BE 2023 44190 7.7
3 BG 2023 10950 5.9
4 CH 2023 82440 7.8
5 CY 2023 29320 7.4
6 CZ 2023 21680 7.4
7 DE 2023 43770 7
8 DK 2023 56770 7.5
9 EE 2023 21580 7.2
10 EL 2023 18600 6.9
# ℹ 23 more rows
Q10. Examine and evaluate the following instruction. Then, explain what it does.
This instruction prints a sorted horizontal bar chart. reorder(country, gdp) orders the categorical country axis sequentially by their corresponding value metric (that of variable gdp). geom_bar(stat = "identity") maps raw values directly to lengths, and coord_flip() rotates the layout by 90 degrees, swapping the x- and y-axes.

Q11. Create an object called data_filtered that contains only the rows of the dataset where the year is either 2013 or 2023.
Use %in% with a two-element vector, rather than two chained == conditions.
# A tibble: 67 × 4
country year gdp life_satisf
<chr> <dbl> <dbl> <dbl>
1 AT 2013 43080 7.8
2 AT 2023 45670 7.7
3 BE 2013 39440 7.5
4 BE 2023 44190 7.7
5 BG 2013 7600 4.8
6 BG 2023 10950 5.9
7 CH 2013 74650 8
8 CH 2023 82440 7.8
9 CY 2013 20830 6.2
10 CY 2023 29320 7.4
# ℹ 57 more rows
Q12. Examine and evaluate the following instruction. Then, explain what it does.
It sorts rows first chronologically by year, then sorts the observations in descending order of cross-sectional gdp within each temporal block.
# A tibble: 67 × 4
country year gdp life_satisf
<chr> <dbl> <dbl> <dbl>
1 LU 2013 102010 7.4
2 CH 2013 74650 8
3 NO 2013 60260 7.9
4 IS 2013 52880 8
5 DK 2013 49750 8
6 IE 2013 44830 7.5
7 NL 2013 44230 7.8
8 SE 2013 43770 7.9
9 AT 2013 43080 7.8
10 FI 2013 41020 8
# ℹ 57 more rows
Q13. Create a barplot ranking countries in decreasing order of GDP, with two panels: the left panel for year 2013, the right panel for year 2023. The plot should have:
- the x-axis titled “GDP”,
- no y-axis title,
- the main title “GDP by Country in 2013 and 2023”.
Use facet_wrap(~year) on top of the same bar chart recipe as Q10, then swap which axis gets the NULL label.
Rank countries by change in GDP between 2013 and 2023
Q14. Create a new variable called change_gdp that measures the within-country change in GDP between 2013 and 2023.
This is a within-country comparison across years, so you need one row per country: reshape the data with pivot_wider() so that each year’s GDP becomes its own column.
pivot_wider(names_from = year, values_from = gdp, names_prefix = "gdp_") gives you columns gdp_2013 and gdp_2023. Then simply subtract them with mutate().
To extract delta values properly across years from wide or long frames:
tb_change <- tb |>
filter(year %in% c(2013, 2023)) |>
select(country, year, gdp) |>
pivot_wider(names_from = year, values_from = gdp, names_prefix = "gdp_") |>
mutate(change_gdp = gdp_2023 - gdp_2013)
tb_change# A tibble: 34 × 4
country gdp_2013 gdp_2023 change_gdp
<chr> <dbl> <dbl> <dbl>
1 AT 43080 45670 2590
2 BE 39440 44190 4750
3 BG 7600 10950 3350
4 CH 74650 82440 7790
5 CY 20830 29320 8490
6 CZ 17970 21680 3710
7 DE 40160 43770 3610
8 DK 49750 56770 7020
9 EE 17630 21580 3950
10 EL 15800 18600 2800
# ℹ 24 more rows
Q15. Examine and evaluate the following instruction. Then, explain what it does.
It isolates cross-sectional observations from 2023 from the data frame tb and assigns the resulting data frame in an object named tb_2023.
# A tibble: 33 × 4
country year gdp life_satisf
<chr> <dbl> <dbl> <dbl>
1 AT 2023 45670 7.7
2 BE 2023 44190 7.7
3 BG 2023 10950 5.9
4 CH 2023 82440 7.8
5 CY 2023 29320 7.4
6 CZ 2023 21680 7.4
7 DE 2023 43770 7
8 DK 2023 56770 7.5
9 EE 2023 21580 7.2
10 EL 2023 18600 6.9
# ℹ 23 more rows
Q16. Create a barplot ranking countries in decreasing order of their change in GDP (between 2013 and 2023). The plot should have:
- the x-axis titled “Change in GDP”,
- no y-axis title,
- the main title “Change in GDP by Country between 2013 and 2023”.
ggplot(data = tb_change, mapping = aes(x = reorder(country, change_gdp), y = change_gdp)) +
geom_bar(stat = "identity") +
coord_flip() +
labs(
x = NULL,
y = "Change in GDP",
title = "Change in GDP by Country between 2013 and 2023"
) +
theme_minimal()Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_bar()`).

Evolution of GDP in France and Italy
Q17. Create an object called tb_france_italy that contains rows of the dataset tb where the country is either “France” or “Italy”.
# A tibble: 14 × 4
country year gdp life_satisf
<chr> <dbl> <dbl> <dbl>
1 FR 2013 34990 7.1
2 FR 2018 36570 7.3
3 FR 2021 36460 6.8
4 FR 2022 37280 7
5 FR 2023 37730 7.1
6 FR 2024 38180 7.1
7 FR 2025 38360 7.1
8 IT 2013 28920 6.6
9 IT 2018 30470 7
10 IT 2021 30760 7.2
11 IT 2022 32310 7.2
12 IT 2023 32630 7.2
13 IT 2024 32900 7.2
14 IT 2025 33080 7.1
Q18. Using the dataset tb_france_italy, create a line plot showing the year-by-year evolution of GDP for France and Italy (one line per country). Include a legend to indicate which line corresponds to each country.
Bonus: use blue (#0055A4) for France and green (#009246) for Italy. Place the legend below the graph. Title the y-axis “GDP”.
Map color = country (and group = country) inside aes() so ggplot2 draws one line per country and builds the legend automatically.
For custom colors, use scale_color_manual(values = c("FR" = ..., "IT" = ...)). For the legend position, set theme(legend.position = "bottom").
ggplot(data = tb_france_italy, mapping = aes(x = year, y = gdp, color = country, group = country)) +
geom_line(linewidth = 1) +
geom_point() +
scale_color_manual(values = c("FR" = "#0055A4", "IT" = "#009246")) +
labs(
x = "Year",
y = "GDP",
title = "Evolution of GDP in France and Italy"
) +
theme_minimal() +
theme(legend.position = "bottom")
Introduction to Programming for Data Analysis — Master 1 in Economics
