Lecture 1 — Setup & Python Syntax¶
Python for Economists · University of Bologna · 2025/2026¶
What we cover today¶
- Setting up your environment (Anaconda + Jupyter)
- Variables and data types
- Operators and expressions
- String formatting
- Conditional statements
- Loops
- Functions
- Exercise: writing your first economic function
How to use this notebook: read the markdown cells, run the code cells in order, and fill in the
# YOUR CODE HEREcells during the exercise. Do not skip ahead — each section builds on the previous one.
1. Setting up your environment¶
1.1 Anaconda¶
We use Anaconda to manage Python and its packages. Anaconda gives you:
- A specific Python version that is stable and reproducible
- A package manager (
conda) to install libraries without conflicts - Jupyter Notebook pre-installed
Download from: https://www.anaconda.com/download
Choose the version for your OS (Windows / macOS / Linux). Accept all defaults during installation.
1.2 Create a New Environment¶
Working in a dedicated environment keeps your course packages separate from other Python projects and ensures everyone runs the same versions. To create one, open a terminal (or the Anaconda Prompt on Windows) and run:
conda create -n Python_for_Economists
conda activate Python_for_Economists
The first command creates a new environment called Python_for_Economists. The second activates it — you should see (Python_for_Economists) appear at the start of your terminal prompt.
Important: if something breaks—for instance after installing or updating a package—you can simply delete the environment and recreate it with the same two commands above. This is much safer than working in the base environment, where a conflict could affect your entire Anaconda installation.
To launch Jupyter from within the environment:
jupyter notebook
You only need to create the environment once. From the next session onward, just activate it and launch Jupyter.
Important: always activate your environment before launching Jupyter. If you skip this step, packages you install may not be available in the notebook.
1.3 Launching Jupyter Notebook¶
Once Anaconda is installed:
- Windows: open Anaconda Navigator → launch Jupyter Notebook
- macOS / Linux: open a terminal and type
jupyter notebook
Your browser will open a file browser. Navigate to your course folder and open this notebook.
1.4 Installing additional packages¶
Later in the course we will need packages not included in Anaconda by default. To install them, open a terminal (or the Anaconda Prompt on Windows) and run:
conda install pandas matplotlib seaborn
pip install eurostat wbdata
We will do this together when needed. For today, no additional installation is required.
1.5 A note on notebooks¶
A Jupyter notebook is made of cells. Two main types:
- Markdown cells (like this one): formatted text, equations, links
- Code cells: Python code that you can run
To run a cell: click on it and press Shift + Enter. Try it on the cell below.
# This is a code cell. Run it with Shift+Enter.
print("Welcome to Python for Economists.")
Welcome to Python for Economists.
2. Variables and data types¶
A variable is a name that points to a value stored in memory. In Python, you do not declare the type — Python infers it automatically.
The four basic types we use constantly in economic data work:
| Type | Example | Used for |
|---|---|---|
int | 2024 | integers, counts, years |
float | 2.1 | continuous values, rates, prices |
str | "Italy" | text, country names, labels |
bool | True | binary indicators, flags |
# Economic variables — all four basic types
year = 2024 # int
gdp_growth = 2.1 # float (annual % change)
country = "Italy" # str
is_eurozone = True # bool
# Check the type of any variable with type()
print(type(year))
print(type(gdp_growth))
print(type(country))
print(type(is_eurozone))
<class 'int'> <class 'float'> <class 'str'> <class 'bool'>
# You can reassign a variable at any time
gdp_growth = 0.3 # 2023 estimate
print(gdp_growth)
# And convert between types
year_as_string = str(year)
print(year_as_string, type(year_as_string))
0.3 2024 <class 'str'>
2.1 Naming conventions¶
Python variable names:
- are case-sensitive:
GDPandgdpare different variables - use snake_case by convention:
gdp_growth, notGDPgrowthorgdpGrowth - cannot start with a number or contain spaces
- cannot be Python reserved words (
if,for,return,class, ...)
Good names are descriptive: inflation_rate is better than x or ir.
3. Operators and expressions¶
Python supports standard arithmetic, comparison, and logical operators.
# Arithmetic operators
gdp_2022 = 1909.0 # Italy GDP in billion EUR
gdp_2023 = 1948.0
growth_rate = (gdp_2023 - gdp_2022) / gdp_2022 * 100
print("Growth rate:", growth_rate, "%")
# Integer division and modulo
print(7 // 2) # integer division → 3
print(7 % 2) # remainder → 1
# Exponentiation
print(2 ** 10) # 2 to the power of 10
Growth rate: 2.0429544264012574 % 3 1 1024
# Comparison operators — always return a bool
inflation = 5.8
ecb_target = 2.0
print(inflation > ecb_target) # True
print(inflation == ecb_target) # False (note: == not =)
print(inflation != ecb_target) # True
# Logical operators
recession = False
high_debt = True
print(recession and high_debt) # False
print(recession or high_debt) # True
print(not recession) # True
True False True False True True
4. String formatting¶
In economic research we constantly produce output — tables, reports, log messages — that mixes text and numbers. Python's f-strings (formatted string literals) are the cleanest way to do this.
country = "Italy"
gdp_growth = 0.73
year = 2023
# f-string: prefix the string with f, put variables in {}
print(f"{country} GDP growth in {year}: {gdp_growth}%")
# Format numbers: control decimal places with :.2f
print(f"{country} GDP growth in {year}: {gdp_growth:.2f}%")
# Alignment formatting in f-strings: {value:align width}
# < aligns left, > aligns right, ^ centers. The number sets the minimum field width in characters.
# Keeping widths consistent across rows and header produces neatly aligned columns.
# Example: {country:<12} reserves 12 characters, left-aligned; {growth:>8.2f} reserves 8, right-aligned, 2 decimal places.
# Useful for tables
print(f"{'Country':<12} {'Growth':>8} {'Year':>6}")
print(f"{'Italy':<12} {0.73:>8.2f} {2023:>6}")
print(f"{'Germany':<12} {-0.30:>8.2f} {2023:>6}")
print(f"{'France':<12} {0.90:>8.2f} {2023:>6}")
Italy GDP growth in 2023: 0.73% Italy GDP growth in 2023: 0.73% Country Growth Year Italy 0.73 2023 Germany -0.30 2023 France 0.90 2023
5. Conditional statements¶
if / elif / else lets your code make decisions. In economic applications: classify observations, flag outliers, apply different formulas to different cases.
Important: Python uses indentation (4 spaces) to define code blocks — there are no curly braces like in R or Stata.
gdp_growth = -0.5 # annual % change
if gdp_growth < 0:
label = "contraction"
elif gdp_growth == 0:
label = "stagnation"
elif gdp_growth < 2:
label = "slow growth"
else:
label = "strong growth"
print(f"GDP growth of {gdp_growth}% → {label}")
GDP growth of -0.5% → contraction
# A common pattern: validate inputs before computing
gdp_base = 0 # this would cause a division by zero
if gdp_base == 0:
print("Error: base period GDP cannot be zero.")
else:
growth = (1950 - gdp_base) / gdp_base * 100
print(f"Growth: {growth:.2f}%")
Error: base period GDP cannot be zero.
6. Loops¶
Loops let you repeat an operation across a collection of values — across countries, years, observations. The two main types are for (iterate over a sequence) and while (repeat until a condition is false).
In data work, for loops are by far the most common.
# for loop over a list
countries = ["Italy", "Germany", "France", "Spain"]
for country in countries:
print(f"Processing data for {country}...")
# write some commands here...
Processing data for Italy... Processing data for Germany... Processing data for France... Processing data for Spain...
# range() generates a sequence of integers
# Useful for iterating over years
for random_name in range(2018, 2024): # 2018, 2019, ..., 2023
print(random_name)
2018 2019 2020 2021 2022 2023
# enumerate() gives you both the index and the value
growth_rates = [1.2, 0.3, -0.5, 1.8]
years = [2020, 2021, 2022, 2023]
for i, rate in enumerate(growth_rates):
print(f"{years[i]}: {rate:+.1f}%") # +.1f forces the sign to appear
2020: +1.2% 2021: +0.3% 2022: -0.5% 2023: +1.8%
# Accumulate results in a list
gdp_levels = [100] # index = 100 in year 0
growth_rates = [0.012, 0.003, -0.005, 0.018, 0.007]
for g in growth_rates:
new_level = gdp_levels[-1] * (1 + g)
gdp_levels.append(new_level)
print("GDP index:", [round(x, 3) for x in gdp_levels])
GDP index: [100, 101.2, 101.504, 100.996, 102.814, 103.534]
# while loop — useful when you don't know in advance how many iterations
# Example: how many years to double GDP at 3% annual growth?
gdp = 1.0
years_elapsed = 0
while gdp < 2.0:
gdp *= 1.03 # gdp = gdp * 1.03
years_elapsed += 1
print(f"GDP doubles in {years_elapsed} years at 3% growth.")
# Rule of 72: a quick mental-math approximation for doubling time under compound growth.
# Divide 72 by the annual growth rate (in %) to get the approximate number of years to double.
# Example: at 3% growth, GDP doubles in roughly 72/3 = 24 years (exact value: ~23.4 years).
print(f"(Rule of 72 approximation: {72 // 3} years)")
GDP doubles in 24 years at 3% growth. (Rule of 72 approximation: 24 years)
7. Functions¶
A function is a reusable block of code that takes inputs (arguments) and returns an output. Functions are the building blocks of any serious data pipeline.
The keyword def defines a function. return specifies what value to send back to the caller.
# Basic function: one input, one output
def annualize_rate(quarterly_rate):
"""Convert a quarterly growth rate to an annualized rate."""
return (1 + quarterly_rate) ** 4 - 1
q_rate = 0.005 # 0.5% per quarter
ann_rate = annualize_rate(q_rate)
print(f"Quarterly {q_rate:.1%} → annualized {ann_rate:.2%}")
q_rate = 0.03 # 0.5% per quarter
ann_rate = annualize_rate(q_rate)
print(f"Quarterly {q_rate:.1%} → annualized {ann_rate:.2%}")
Quarterly 0.5% → annualized 2.02% Quarterly 3.0% → annualized 12.55%
# Multiple arguments and a default argument
def gdp_growth_rate(gdp_current, gdp_previous, as_percent=True):
"""
Compute GDP growth rate between two periods.
Parameters
----------
gdp_current : float
GDP in the current period.
gdp_previous : float
GDP in the previous period.
as_percent : bool, optional
If True (default), return the rate in percentage points.
Returns
-------
float
Growth rate.
"""
if gdp_previous <= 0:
raise ValueError("Base period GDP must be strictly positive.")
rate = (gdp_current - gdp_previous) / gdp_previous
return rate * 100 if as_percent else rate
print(gdp_growth_rate(1948, 1909)) # returns percent
print(gdp_growth_rate(1948, 1909, as_percent=False)) # returns decimal
2.0429544264012574 0.020429544264012573
# Functions can return multiple values (as a tuple)
def describe_series(values):
"""Return mean, min and max of a numeric list."""
mean = sum(values) / len(values)
return mean, min(values), max(values)
growth_rates = [1.2, 0.3, -0.5, 1.8, 0.7, -1.1]
avg, low, high = describe_series(growth_rates)
print(f"Mean: {avg:.2f} | Min: {low:.2f} | Max: {high:.2f}")
Mean: 0.40 | Min: -1.10 | Max: 1.80
# Calling a function inside a loop — a very common pattern
data = [
("Italy", 1948, 1909),
("Germany", 4072, 4082),
("France", 2794, 2782),
]
print(f"{'Country':<12} {'Growth':>8}")
print("-" * 22)
for country, gdp_now, gdp_prev in data:
g = gdp_growth_rate(gdp_now, gdp_prev)
print(f"{country:<12} {g:>+8.2f}%")
Country Growth ---------------------- Italy +2.04% Germany -0.24% France +0.43%
8. Exercise¶
Time: ~25 minutes. Work individually.
You are given three pairs of GDP values (in billion EUR) for three countries across two years. Your task is to write a small program that computes and reports growth rates.
Tasks¶
Task 1. Write a function gdp_growth(gdp_t1, gdp_t0) that:
- computes the percentage growth rate between two periods
- raises a
ValueErrorwith an informative message ifgdp_t0is zero or negative - raises a
ValueErrorif either argument is not a number
Task 2. Apply the function to the three data points below using a for loop and print the results in a formatted table.
Task 3. Inside the loop, add a conditional that labels each country's performance:
- growth < 0 →
"contraction" - 0 ≤ growth < 1 →
"stagnation" - 1 ≤ growth < 3 →
"moderate growth" - growth ≥ 3 →
"strong growth"
Task 4 (optional, harder). Write a second function compound_growth(gdp_start, gdp_end, n_years) that computes the compound annual growth rate (CAGR) over n_years. Apply it to each country assuming the two data points are 5 years apart.
Data:
Italy: gdp_2019 = 1789.0, gdp_2023 = 2010.0
Germany: gdp_2019 = 3449.0, gdp_2023 = 4082.0
Spain: gdp_2019 = 1245.0, gdp_2023 = 1419.0
Expected output (exact formatting is up to you):
Country Growth Label
---------------------------------------
Italy +12.35% moderate growth
Germany +18.35% strong growth
Spain +13.98% moderate growth
# Task 1 — write your function here
def gdp_growth(gdp_t1, gdp_t0):
# YOUR CODE HERE
pass
# Tasks 2 & 3 — loop and formatted output
data = [
("Italy", 2010.0, 1789.0),
("Germany", 4082.0, 3449.0),
("Spain", 1419.0, 1245.0),
]
# YOUR CODE HERE
# Task 4 (optional) — CAGR function
def compound_growth(gdp_start, gdp_end, n_years):
# YOUR CODE HERE
pass
Solution¶
This cell is hidden during the exercise. The instructor will walk through the solution at the end of the session.
# ── SOLUTION ──────────────────────────────────────────────────────────────────
# Task 1
def gdp_growth(gdp_t1, gdp_t0):
"""Compute percentage GDP growth rate from t0 to t1."""
if not isinstance(gdp_t1, (int, float)) or not isinstance(gdp_t0, (int, float)):
raise ValueError("Both arguments must be numeric.")
if gdp_t0 <= 0:
raise ValueError(f"Base period GDP must be strictly positive (got {gdp_t0}).")
return (gdp_t1 - gdp_t0) / gdp_t0 * 100
# Tasks 2 & 3
data = [
("Italy", 2010.0, 1789.0),
("Germany", 4082.0, 3449.0),
("Spain", 1419.0, 1245.0),
]
print(f"{'Country':<12} {'Growth':>8} {'Label'}")
print("-" * 42)
for country, gdp_t1, gdp_t0 in data:
g = gdp_growth(gdp_t1, gdp_t0)
if g < 0:
label = "contraction"
elif g < 1:
label = "stagnation"
elif g < 3:
label = "moderate growth"
else:
label = "strong growth"
print(f"{country:<12} {g:>+7.2f}% {label}")
# Task 4
def compound_growth(gdp_start, gdp_end, n_years):
"""Compute CAGR over n_years."""
if gdp_start <= 0:
raise ValueError("Starting GDP must be strictly positive.")
if n_years <= 0:
raise ValueError("Number of years must be positive.")
return ((gdp_end / gdp_start) ** (1 / n_years) - 1) * 100
print("\nCAGR (5 years):")
for country, gdp_t1, gdp_t0 in data:
cagr = compound_growth(gdp_t0, gdp_t1, 5)
print(f" {country:<12} {cagr:.2f}% per year")
Country Growth Label ------------------------------------------ Italy +12.35% strong growth Germany +18.35% strong growth Spain +13.98% strong growth CAGR (5 years): Italy 2.36% per year Germany 3.43% per year Spain 2.65% per year
What we covered today¶
| Concept | Key takeaway |
|---|---|
| Variables & types | Python infers types; use descriptive snake_case names |
| Operators | Arithmetic, comparison, logical — all return values |
| f-strings | Cleanest way to mix text and numbers in output |
| Conditionals | if / elif / else — indentation defines the block |
| Loops | for iterates over sequences; while iterates until a condition fails |
| Functions | def + return; add docstrings; validate inputs |
Next lecture¶
Lecture 2 — Data structures: lists, dictionaries, tuples, sets, and list comprehensions. We will also introduce the logic of object-oriented programming.
Please make sure Anaconda is installed and working before then.