Lecture 1 — Setup & Python Syntax¶

Python for Economists · University of Bologna · 2025/2026¶


What we cover today¶

  1. Setting up your environment (Anaconda + Jupyter)
  2. Variables and data types
  3. Operators and expressions
  4. String formatting
  5. Conditional statements
  6. Loops
  7. Functions
  8. 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 HERE cells 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.

In [1]:
# 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
In [2]:
# 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'>
In [3]:
# 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: GDP and gdp are different variables
  • use snake_case by convention: gdp_growth, not GDPgrowth or gdpGrowth
  • 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.

In [4]:
# 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
In [5]:
# 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.

In [6]:
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.

In [7]:
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
In [8]:
# 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.

In [9]:
# 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...
In [10]:
# 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
In [11]:
# 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%
In [12]:
# 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]
In [13]:
# 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.

In [14]:
# 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%
In [15]:
# 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
In [16]:
# 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
In [17]:
# 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 ValueError with an informative message if gdp_t0 is zero or negative
  • raises a ValueError if 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
In [18]:
# Task 1 — write your function here

def gdp_growth(gdp_t1, gdp_t0):
    # YOUR CODE HERE
    pass
In [19]:
# 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
In [20]:
# 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.

In [21]:
# ── 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.